Index: /tdiary/branches/upstream/contrib/Rakefile
===================================================================
--- /tdiary/branches/upstream/contrib/Rakefile (revision 710)
+++ /tdiary/branches/upstream/contrib/Rakefile (revision 710)
@@ -0,0 +1,86 @@
+# Rakefile for building tdiary-conrib package
+require 'rake'
+require 'rake/clean'
+require 'rake/packagetask'
+require 'rake/testtask'
+require 'spec/rake/spectask'
+
+package = {
+	:name         => 'tdiary-contrib',
+	:root         => File.expand_path(File.dirname(__FILE__)),
+	:include_dirs => %w[doc filter lib misc plugin spec test util].map{|d| File.join d, '**', '*' },
+	:binary_ext   => %w[swf].map{|ext| ".#{ext}" },
+}
+package[:pkgdir] = File.join package[:root], 'package'
+package[:rev]    = 'r' << `svnversion --no-newline --committed #{package[:root]}`[/\d+[MS]{0,2}$/]
+package.freeze
+
+Rake::TestTask.new do |t|
+	t.libs << File.join(package[:root], 'plugin')
+	t.pattern = File.join 'test', '**', '*_test.rb'
+end
+
+Spec::Rake::SpecTask.new(:spec) do |t|
+	t.spec_opts << '--colour'
+	t.spec_opts << '--options' << File.join('spec', 'spec.opts')
+end
+
+namespace :spec do
+	desc "Run all specs with RCov"
+	Spec::Rake::SpecTask.new(:rcov) do |t|
+		t.spec_opts << '--colour'
+		t.spec_opts << '--options' << File.join('spec', 'spec.opts')
+		t.rcov = true
+		t.rcov_opts = lambda do
+			IO.readlines(File.join('spec', 'rcov.opts')).map {|l| l.chomp.split " "}.flatten
+		end
+	end
+
+	namespace :rcov do
+		task :clean do
+			rm_rf "coverage"
+		end
+	end
+end
+
+desc 'Update source and packaging'
+task :default => [:update, :package, :clean]
+
+desc 'Update files from Subversion Repository'
+task :update do |t|
+	sh 'svn', 'update', package[:root]
+end
+
+pkg = Rake::PackageTask.new(package[:name], package[:rev]) do |p|
+	p.package_dir = package[:pkgdir]
+	p.package_files.include package[:include_dirs]
+	p.need_tar_gz  = true
+	#p.need_tar_bz2 = true
+end
+
+desc 'Convert source encoding from UTF-8 to EUC-JP'
+task :to_euc do |t|
+	require 'shell'
+	pkg.package_files.each do |f|
+		filename = File.join pkg.package_dir_path, f
+		# exclude directories and binary files
+		next if File.ftype(filename) != 'file' ||
+		        package[:binary_ext].include?(File.extname(filename))
+
+		case
+		when Shell.new.find_system_command('nkf')
+			sh <<-EOS.gsub(/^\s+/, '')
+				nkf -O --euc #{filename}{,.tmp} && \\
+				touch -m --reference=#{filename} #{filename}.tmp && \\
+				mv #{filename}{.tmp,}
+			EOS
+		when Shell.new.find_system_command('iconv')
+			sh <<-EOS.gsub(/^\s+/, '')
+				iconv --from-code=utf-8 --to-code=euc-jp --output #{filename}{.tmp,} && \\
+				touch -m --reference=#{filename} #{filename}.tmp && \\
+				mv #{filename}{.tmp,}
+			EOS
+		end
+	end
+end
+
Index: /tdiary/branches/upstream/contrib/doc/en/spambayes.txt
===================================================================
--- /tdiary/branches/upstream/contrib/doc/en/spambayes.txt (revision 710)
+++ /tdiary/branches/upstream/contrib/doc/en/spambayes.txt (revision 710)
@@ -0,0 +1,3 @@
+For detail
+ English : http://www.hinet.mydns.jp/en/?SpamBayes
+ Japanese : http://www.hinet.mydns.jp/?SpamBayes
Index: /tdiary/branches/upstream/contrib/doc/en/README.txt
===================================================================
--- /tdiary/branches/upstream/contrib/doc/en/README.txt (revision 710)
+++ /tdiary/branches/upstream/contrib/doc/en/README.txt (revision 710)
@@ -0,0 +1,10 @@
+== tDiary contrib package ==
+
+This package include some utilities and plugins for tDiary.
+See documents in each directories.
+
+See licenses and copyrights in each theme files. If no license
+or copyright, it can be distoributed by GPL2, and it has a
+copyright below: 
+ 
+   Copyright (c) 2005 TADA Tadashi <sho@spc.gr.jp> 
Index: /tdiary/branches/upstream/contrib/doc/en/util.txt
===================================================================
--- /tdiary/branches/upstream/contrib/doc/en/util.txt (revision 710)
+++ /tdiary/branches/upstream/contrib/doc/en/util.txt (revision 710)
@@ -0,0 +1,1 @@
+== tDiary contrib/util ==
Index: /tdiary/branches/upstream/contrib/doc/en/plugin.txt
===================================================================
--- /tdiary/branches/upstream/contrib/doc/en/plugin.txt (revision 710)
+++ /tdiary/branches/upstream/contrib/doc/en/plugin.txt (revision 710)
@@ -0,0 +1,1 @@
+== tDiary contrib/plugin ==
Index: /tdiary/branches/upstream/contrib/doc/ja/select_theme.txt
===================================================================
--- /tdiary/branches/upstream/contrib/doc/ja/select_theme.txt (revision 710)
+++ /tdiary/branches/upstream/contrib/doc/ja/select_theme.txt (revision 710)
@@ -0,0 +1,1 @@
+See: http://arika.org/diary/20050421#p01
Index: /tdiary/branches/upstream/contrib/doc/ja/livedoor_weather.txt
===================================================================
--- /tdiary/branches/upstream/contrib/doc/ja/livedoor_weather.txt (revision 710)
+++ /tdiary/branches/upstream/contrib/doc/ja/livedoor_weather.txt (revision 710)
@@ -0,0 +1,105 @@
+! 概要
+[[Weather Hacks - livedoor 天気情報|http://weather.livedoor.com/weather_hacks/]]を利用して日記に天気情報を埋め込むプラグイン
+
+!使い方
+インストールしたら、設定画面で天気情報を取得する都市IDを設定してください。日記更新時に天気情報を取得します。
+デフォルトでは日記の一日単位上部に天気情報を埋め込みます。
+
+!!CSS
+ div.lwws {
+	  text-align: right;
+	  font-size: 0.8em;
+ }
+上記のように設定すると見やすくなります。
+
+!!キャッシュファイル
+天気情報はxml形式で
+ @cache_path/lwws/YYYYMMDD.xml
+というファイルに保存しています。
+
+!!プラグイン呼び出し
+!!!lwws_today
+今日の天気を表示します。
+!!!lwws_tomorrow
+明日の天気を表示します。
+!!!lwws_dayaftertomorrow
+明後日の天気を表示します。
+
+!!!使い方
+例えば(Wikiスタイルの例)
+ ||今日||明日||明後日
+ ||{{lwws_today}}||{{lwws_tomorrow}}||{{lwws_dayaftertomorrow}}
+と書くと三日分の天気が表で表示されます。
+サイドバーなどに使うとかっこいいかもしれません。
+
+!ToDo
+*セキュア対応
+*キャッシュの削除機能
+*(他にあれば募集します)
+
+!ライセンス
+GPL2
+
+!更新履歴
+!!20070329
+* open-uri の proxy を tdiary.conf から読み込むようにした
+
+!!20070214
+* feed 生成時の出力を抑制
+
+!!20070206
+* NKFを使うのをやめて @conf.to_native を使うようにした
+* proxy の削除(trunk に追従)
+* to_native の第二引数として utf-8 を指定
+
+!!20070109
+* サニタイズキャンペーンに伴う修正
+
+!!20060712
+* lwws_getのバグ修正
+
+!!20060709
+* 設定更新時には強制的にキャッシュを再取得するように変更
+
+!!20060702
+* lwws_getの処理を変更
+
+!!20060310
+* 設定が保存されない不具合の修正
+
+!!20060309
+* キャッシュの自動更新を設定可能にする
+* 自動更新間隔を任意で設定可能にする
+** 自動更新間隔を0にするとアクセスするたびにLWWSをたたくようになるので、適当に6-12くらいの数字を入れるのを推奨。
+
+!!20060219
+* 携帯閲覧時には画像を非表示
+* 画像にtitle要素としてtelopエレメントを追加
+
+!!20060213
+* キャッシュファイルを6時間で更新するようにした。
+* 任意の日付の天気を表示するlwwsメソッドを追加
+
+!!20060212
+* 新規メソッドとしてlwws_today,lwws_tomorrow,lwws_dayaftertomorrowを追加。プラグイン呼び出しとして、任意の場所に天気を表示します。
+* 表示項目を変更機能の追加(設定画面)。
+* アイコン表示機能の追加(設定画面)。
+* 詳細へのリンクは天候に作成するようにした。(アイコンの場合はアイコンに設定)
+* convert_dateメソッドを追加。date_statusからYYYYMMDD形式の文字列を返します。
+* 英語リソースをでっちあげた。(中国語は英語リソースの焼きまわし)
+* 「℃」を言語リソースに移動
+* lwws_to_thmlメソッドで任意の日付を指定可能にした。
+
+
+!!20060211
+* lwws_getメソッドの引数をdateからdate_status(today,tomorrow,dayaftertomorrow)へと変更
+* 設定画面の不要なエスケープを修正
+* lwws_getメソッドでxmlを保存する時にYYYYMMDD形式になっていないバグを修正
+
+!!20060210
+* 最初のリリース
+* Uconvではなく、NKFを使ってUTF-8を扱うようにした。
+* typo(というか、コピペ修正ミス)を直した。
+* 最高気温、最低気温が存在する場合には表示するようにした。
+* livedoor天気情報へのリンクを作るようにした。
+* proxyの情報はamazon.proxyに統合した。
Index: /tdiary/branches/upstream/contrib/doc/ja/cocoment.txt
===================================================================
--- /tdiary/branches/upstream/contrib/doc/ja/cocoment.txt (revision 710)
+++ /tdiary/branches/upstream/contrib/doc/ja/cocoment.txt (revision 710)
@@ -0,0 +1,31 @@
+! 名前
+coCommentプラグイン
+
+! 概要
+tDiaryをcoComment(http://www.cocomment.com/)に対応させるプラグイン
+
+!使い方
+プラグインフォルダに入れるだけで動作します。
+
+!ライセンス
+GPL2
+
+!更新履歴
+!!20070309
+* mobile_agent? の使用方法を修正
+
+!!20070213
+* ケータイ端末からのアクセスは無視するようにした
+
+!!20070212
+* IE7でのスクリプトエラーに対処(埋め込む Javascript を微調整)
+
+!!20070118
+* postURLとpostTitleを取得するときにjavascriptを使わないようにした。
+
+!!20070117
+* tDiary-contrib 追加に伴って name 属性 comment-form を core に追加
+* cocomment-fetchlet を追加
+
+!!20060527
+* 初版公開
Index: /tdiary/branches/upstream/contrib/doc/ja/antirefspam.history.txt
===================================================================
--- /tdiary/branches/upstream/contrib/doc/ja/antirefspam.history.txt (revision 710)
+++ /tdiary/branches/upstream/contrib/doc/ja/antirefspam.history.txt (revision 710)
@@ -0,0 +1,59 @@
+=begin
+
+ver 1.0 2005/06/26
+	・更新履歴を別ファイルにした
+	・リンク元のチェックの有無を設定できるようにした (thanks to Kazuhiro NISHIYAMA)
+	・ブロックしたツッコミをログ(spamcomments)に残すようにした
+	・一度も設定を行っていない場合にエラーが起きていた不具合を修正 (thanks to Kazuhiro NISHIYAMA)
+	・"トップページURL" が相対指定になっているときに、チェックをすり抜けてしまうことがあった
+	　不具合を修正 (thanks to Kazuhiro NISHIYAMA)
+	・"トップページURL" と同様に base_url でもチェックするようにした (thanks to Kazuhiro NISHIYAMA)
+	・"「リンク元置換リスト」にマッチするリンク元を信頼する" を有効にしており、かつリンク元の
+	　URLの長さが50文字以上だった場合に、チェックをすり抜けてしまっていた不具合を修正
+	　 (thanks to Hiroshi Koyasu)
+
+ver 0.9 2004/11/24
+	・リンク元置換リストにマッチするリンク元を信頼する機能を追加 (thanks to Shun-ichi TAHARA)
+	・コメントの制限に正規表現を使えるようにした
+	・HTTP.version_1_2 系を使えなかった場合に動作がおかしかった不具合を修正
+	・spamips に出力される時刻の分/秒部分がおかしかったのを修正
+	・その他エラーが起きにくいように処理を変更
+
+ver 0.8 2004/11/15
+	・プロキシーサーバーを指定する機能を追加
+	・ver 0.6m〜0.71 で、Ruby 1.6 系でエラーが出ることがあった不具合を修正
+
+ver 0.71 2004/11/12
+	・ver 0.6m と ver 0.7 で、"信頼するリンク元" の指定が適用されなくなっていた不具合を修正
+
+ver 0.7 2004/11/11
+	・一部のアンテナで、設定によって更新日時が取れないことがあった問題に対処
+	・コメントスパムに対処するため、コメントに制限をかける機能を追加
+
+ver 0.6m 2004/11/07 (MoonWolf)
+	・ソースのインデント変更
+	・if not→unlessへの書き換え等
+
+ver 0.6 2004/11/07
+	・トップページURL以外の許容するリンク先を指定できるようにした。
+	・設定画面の言語リソースを分割した。
+
+ver 0.5 2004/10/31
+	・信頼できるURL に正規表現を使えるようにした
+	・safeurls, spaurls に、同一の URL が２つ連続で記録される問題に対処した(つもり)
+
+ver 0.4 2004/10/20
+	・Ruby 1.8.2 (preview2) で動作しなかった不具合を修正
+	・接続するポートを80からuri.portに変更 (thanks to MoonWolf)
+
+ver 0.3 2004/09/30
+	・負荷を下げるための修正をちょっとだけ入れた
+
+ver 0.2 2004/09/27
+	・信頼できるURLの一覧を設定画面から変更できるようにした
+
+ver 0.1 2004/09/15
+	・最初のバージョン
+
+=end
+
Index: /tdiary/branches/upstream/contrib/doc/ja/antirefspam.txt
===================================================================
--- /tdiary/branches/upstream/contrib/doc/ja/antirefspam.txt (revision 710)
+++ /tdiary/branches/upstream/contrib/doc/ja/antirefspam.txt (revision 710)
@@ -0,0 +1,86 @@
+#
+# AntiRefererSpam Plugin
+#
+# version 1.0.0G  (2005/08/01)
+#
+# Copyright (c) 2004-2005 T.Shimomura <redbug@netlife.gr.jp>
+# You can redistribute it and/or modify it under GPL2.
+# Please use version 1.0.0 (not 1.0.0G) if GPL doesn't want to be forced on me.
+#
+
+・これはなにをするもの？
+	tDiary の「本日のリンク元」に、主に海外のアダルトサイトからの大量の
+	アクセス履歴（リファラスパム）が残ってしまうのを防ぐためのプラグイン
+	（厳密にはフィルター）です。
+
+	リンク元URLが指すページのHTMLに、日記のアドレスが含まれていなければ
+	不正なリンク元であるとみなします。
+	（今のところはこれだけの対処で99%近くをブロックできているみたいです)
+
+	また、コメントスパムに対処するために、コメントに対して制限をかける
+	機能もついています。
+
+
+・インストール
+	tDiary はバージョン 2.0.0 以降が必要です。2.1 系での動作は未確認。
+
+	tDiary をインストールしたディレクトリを $(tdiary_home) としたとき、
+	plugin/antirefspam.rb を $(tdiary_home)/plugin に、
+	plugin/ja/antirefspam.rb を $(tdiary_home)/plugin/ja に、
+	tdiary/filter/antirefspam.rb を $(tdiary_home)/tdiary/filter に、
+	それぞれコピーしてください。
+
+	本プラグインの古いバージョンがある場合は上書きしてかまいません。
+	古いバージョンで問題なく動作していた場合は、設定を変更する必要も
+	ありません。
+
+
+・最低限必要な設定
+	tDiary の設定画面で、"Anti Referer Spam" の "許容するリンク先の指定"
+	を確認してください。
+	設定画面に、"トップページURL" と "日記のURL" がカッコつきで書かれて
+	いるはずです。これら以外にリンク先のURLとして許容したいものがあれば、
+	それを http〜で始まる絶対パス指定で書いてください。
+
+	また、tDiary をインストールしてあるサーバーが外部の HTTP サーバーに
+	アクセスする際に HTTP プロキシを経由する必要がある場合は、その設定
+	も行ってください。
+
+
+・さらに細かい設定
+	[最低限必要な設定]のみでも効果があるはずですが、tDiary の設定画面で
+	 "Anti Referer Spam" の「信頼するリンク元の指定」を設定することで、
+	 負荷が軽くなるはずです。
+
+	信頼するリンク元には、いくつかのサーチエンジンやアンテナを書いておく
+	とよいでしょう。
+	詳細な書式については、設定画面に表示されるヒントを参照してください。
+
+
+・プラグインが生成するファイル
+	このプラグインは、日記データが存在するディレクトリにAntiRefSpamFilter
+	というディレクトリを作って、その下にいくつかのファイルを作成します。
+		safeurls     問題ないリンク元とみなした URL の一覧
+		spamurls     不正なリンク元とみなした URL の一覧
+		spamips      不正なリンク元とみなした IP の一覧
+		spamcomments 不正なコメントとみなしたコメントの一覧
+
+	safeurls に入るべき URL が、spamurls に登録されてしまった場合は、
+	その URL を、「信頼するリンク元の指定」で設定してください。
+
+	spamurls に入るべき URL が、safeurls に登録されてしまった場合は、
+	このプラグインを強化する必要があります。
+	・このプラグインのバージョン
+	・日記の URL
+	・誤判定されてしまった URL
+	を作者に教えてもらえれば対応するかもしれません。
+
+
+・FAQ
+	以下のページにある FAQ を適宜更新します。
+	http://www.netlife.gr.jp/redbug/diary/?date=20041018#p02
+
+
+・連絡先
+	T.Shimomura <redbug@netlife.gr.jp>
+
Index: /tdiary/branches/upstream/contrib/doc/ja/category_to_tag.txt
===================================================================
--- /tdiary/branches/upstream/contrib/doc/ja/category_to_tag.txt (revision 710)
+++ /tdiary/branches/upstream/contrib/doc/ja/category_to_tag.txt (revision 710)
@@ -0,0 +1,21 @@
+category_to_tag.rb - カテゴリをタグ風に表示します。 
+
+※tDiary 2.1.3.20051012以降でのみ動作します。
+
+■使い方 
+インストールするだけで使えるようになります。日記モードのcategory.rbか、
+BlogKitのblog-category.rbのいずれかと併用して使います。 
+
+なお、これらの併用プラグインよりもあとに読み込まれなければいけません。
+プラグイン選択を使う場合には@options['sp.path']で指定したパスの最後の
+ディレクトリに入れるか、いっそプラグイン選択を使わずに、tDiary本体の
+pluginディレクトリに入れてしまうのがよいでしょう。 
+
+なお、タグの一覧はスタイルシートで「div.tags」を指定することで見栄え
+を変更できます。append-css.rbを使うといいでしょう。 
+
+例: 
+   div.day div.tags {
+      text-align: right;
+      font-size: 80%;
+   }
Index: /tdiary/branches/upstream/contrib/doc/ja/add_bookmark.txt
===================================================================
--- /tdiary/branches/upstream/contrib/doc/ja/add_bookmark.txt (revision 710)
+++ /tdiary/branches/upstream/contrib/doc/ja/add_bookmark.txt (revision 710)
@@ -0,0 +1,50 @@
+!概要
+subtitleの右にソーシャルブックマークへのリンクを埋め込みます。
+
+!使い方
+プラグインフォルダに配置するだけで使用可能です。
+プラグイン選択画面で有効に変更後、設定画面で埋め込みたいブックマークを選択します。
+
+!ライセンス
+GPL2
+
+!更新履歴
+!!20070215
+* サービスから MM/Memo を削除
+* サービスに livedoor クリップ と Buzzurl を追加
+
+!!20070109
+* サニタイズキャンペーンに伴う修正
+	
+!!20050906
+* HTML4.01 Strictに準拠
+* セクションのURLをエスケープするように変更
+
+!!20050901
+* はてなブックマークのURLを修正
+
+!!20050828
+* add_subtitle_procに対応
+* webshots,LiVEMARK,fc2ブックマークを削除
+* 強制的にアイコンを使用するように設定
+
+!!20050602
+* はてなブックマークのアイコンの修正 
+* del.icio.usのURLの修正 
+
+!!20050601
+* LiVEMARKに対応 
+* はてなブックマーク、del.icio.us、MM/Memoをアイコンで表示できるようにした。
+
+!!20050528
+* @confの値とdayモードの判定方法を変更 
+* 処理でイテレータを使うように変更 
+* 英語リソースの追加 
+* webshotsに対応 
+* fc2ブックマークに対応 
+
+!!20050525
+* 初版公開
+
+!参考情報
+add_conf_procのチェックボックス処理についてはsearch_control.rb、body_enter_procについてはrandom_google.rbを参考にしました。
Index: /tdiary/branches/upstream/contrib/doc/ja/google_adsense.txt
===================================================================
--- /tdiary/branches/upstream/contrib/doc/ja/google_adsense.txt (revision 710)
+++ /tdiary/branches/upstream/contrib/doc/ja/google_adsense.txt (revision 710)
@@ -0,0 +1,25 @@
+google_adsense.rb - Googe AdSenseのバナー広告を挿入するプラグイン
+
+
+
+プラグイン選択で選択し、日記のヘッダやフッタなど、任意の場所に以下の
+ように指定するだけで使えます。
+
+   <%= google_adsense %> 
+
+バナーのサイズや色は、設定画面から変更可能です。
+
+なお、配布されているgoogle_adsense.rbには、tDiary.Net <http://www.tdiary.net/>
+用のAdSense IDが書かれているので、このまま使うとtDiary.Netの広告収入
+になってしまいます。自分のAdSense IDを使いたい場合には、ファイルの最
+初の方にある以下の部分を、自分のIDに合わせて書き換えてください。
+
+   google_ad_client = "pub-3317603667498586" 
+                       ~~~~~~~~~~~~~~~~~~~~この部分がID 
+
+
+2005-10-12追記
+  セクションターゲット(※)に対応しました。タグは日記の各日付の最初と最後に挿入
+  されます。
+
+  ※https://www.google.com/support/adsense/bin/answer.py?answer=23168&topic=371
Index: /tdiary/branches/upstream/contrib/doc/ja/section_footer.txt
===================================================================
--- /tdiary/branches/upstream/contrib/doc/ja/section_footer.txt (revision 710)
+++ /tdiary/branches/upstream/contrib/doc/ja/section_footer.txt (revision 710)
@@ -0,0 +1,84 @@
+! 概要
+セクションの後ろにナビゲーションを追加するプラグイン。
+
+以下の項目が追加されます。
+
+* タグ(カテゴリ)
+* このエントリを含むはてなブックマーク
+* はてなブックマークコメント表示
+* このエントリを含む del.icio.us
+* このエントリを含む livedoor クリップ
+* このエントリを含む Buzzurl
+* Permalink
+
+!注意事項
+* blogkitでは動きません
+* セキュアな環境では動きません
+* category_to_tag.rb と同時に使うことはできません
+* Permalinkやアイコンは用途に合わせてadd_section_leave_procから適当に削ってください
+* 利用するには [[JSON library for Ruby|http://rubyforge.org/projects/json/]] が必要(lib 以下に配置済み)
+
+! 動作事例
+http://www.hsbt.org/diary/
+
+!ライセンス
+GPL2
+
+! Changelog
+!!20070323
+* del.icio.us Image API 使用時に ?aggregate を追加
+
+!!20070322
+* 画像取得API に対応
+
+!!20070321
+* JSON library for Ruby を追加
+
+!!20070215
+* アイコンを本家から持ってくるようにした
+
+!!20070212
+* Buzzurl(http://buzzurl.jp) に対応した
+
+!!20070210
+* Permalink 生成時の余計なエスケープをしないようにした
+
+!!20070206
+* NKFを使うのをやめて @conf.to_native を使うようにした
+* to_native の第二引数として utf-8 を指定
+
+!!20070205
+* delicious JSON のキャッシュデータは @cache/delicious/YYYYMM に作成するようにした
+
+!!20070204
+* 全面的に見直し、追加関係のリンクは生成しないようにした
+* del.icio.us の JSON API に対応、ブックマーク数を表示できるようにした
+* はてなブックマーク、livedoor クリップのブックマーク数画像表示APIに対応させた
+* 上記のAPIを使うようにしたため、まちゅさんのブックマークカウントキャッシュは使わないようにした
+
+!!20060107
+* リンク追加処理をメソッドとして分離
+
+!!20051229
+* [[人気の日記プラグイン (はてなブックマーク + MM/Memo) - まちゅダイアリー (2005-12-27)|http://www.machu.jp/diary/20051227.html#p03]]の改造の取り込み
+
+!!20051227
+* [[人気の日記プラグイン (はてなブックマーク) - まちゅダイアリー (2005-12-27)|http://www.machu.jp/diary/20051227.html#p02]]の改造の取り込み
+
+!!20051207
+* 「このエントリを含むdel.icio.us」のリンクを追加
+
+!!20051129
+* 「このエントリを含むMM/Memo」のリンクを追加
+
+!!20051124
+* 「このエントリをdel.icio.usに追加」のリンクを追加
+
+!!20051121
+* 携帯からのアクセスの時には表示しないようにした。
+
+!!20051114
+* 最初のリリース
+
+! 謝辞
+このプラグインはたださんが作ったcategory_to_tag.rbとえろぺおさんによる[[tDiary の指定したセクションの permalink を求める|http://bigfield.ddo.jp/diary/20051026.html#p01]]をインスパイヤしました。ありがとうございました!
Index: /tdiary/branches/upstream/contrib/doc/ja/google_analytics.txt
===================================================================
--- /tdiary/branches/upstream/contrib/doc/ja/google_analytics.txt (revision 710)
+++ /tdiary/branches/upstream/contrib/doc/ja/google_analytics.txt (revision 710)
@@ -0,0 +1,7 @@
+google_analytics.rb - Googe Analyticsの解析用コードを挿入する
+
+
+
+プラグイン選択で選択し、設定画面でProfile IDを指定すれば使えます。
+Profile IDは、Analyticsのページでサイトを登録すると表示されるJavaScript中にある、
+「UA-xxxxx-x」のうち、「xxxxx-x」の部分のことです。
Index: /tdiary/branches/upstream/contrib/doc/ja/spambayes.txt
===================================================================
--- /tdiary/branches/upstream/contrib/doc/ja/spambayes.txt (revision 710)
+++ /tdiary/branches/upstream/contrib/doc/ja/spambayes.txt (revision 710)
@@ -0,0 +1,3 @@
+For detail
+ Japanese : http://www.hinet.mydns.jp/?SpamBayes
+ English : http://www.hinet.mydns.jp/en/?SpamBayes
Index: /tdiary/branches/upstream/contrib/doc/ja/comment_key.ja.html
===================================================================
--- /tdiary/branches/upstream/contrib/doc/ja/comment_key.ja.html (revision 710)
+++ /tdiary/branches/upstream/contrib/doc/ja/comment_key.ja.html (revision 710)
@@ -0,0 +1,182 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<!-- 美乳 -->
+<html lang="ja">
+  <head>
+    <meta http-equiv="Content-type" content="text/html; charset=UTF-8">
+    <meta http-equiv="Content-Script-Type" content="text/javascript">
+    <meta name="author" content="Hahahaha">
+    <link rev="made" href="mailto:rin_ne-at-big.or.jp">
+    <link rel="home" href="http://www20.big.or.jp/~rin_ne/">
+    <meta http-equiv="content-style-type" content="text/css">
+    <link rel="stylesheet" href="../../fortune.css" type="text/css" media="all">
+    <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www20.big.or.jp/~rin_ne/tdiary/index.rdf">
+    <title>Comment-key Filter &amp; Plugin</title>
+  </head>
+
+  <body>
+
+    <div id="headerArea">
+      <h2>コメントキーフィルタ＆プラグイン</h2>
+    </div>
+
+    <div id="contentArea">
+
+      <div class="sectionList">
+        <ul>
+          <li><a href="#abstruct">はじめに</a></li>
+          <li><a href="#system">動作環境</a></li>
+          <li><a href="#install">インストール・アンインストール</a></li>
+          <li><a href="#usage">使い方</a></li>
+          <li><a href="#algorithm">動作原理</a></li>
+          <li><a href="#faq">FAQ</a></li>
+          <li><a href="#sorry">おことわり</a></li>
+          <li><a href="#license">ライセンスなど</a></li>
+          <li><a href="#thanks">謝辞</a></li>
+          <li><a href="#changelog">履歴</a></li>
+          <li><a href="#prevver">過去のバージョン</a></li>
+        </ul>
+      </div>
+
+      <div id="contentBody">
+
+        <div class="section">
+          <h3><a name="abstruct">はじめに</a></h3>
+          <div class="secbody">
+            <p>最近、所有者以外が書き込みできるタイプのページ（掲示板やコメント、トラックバックなど）に宣伝目的で大量のデータを書き込む、いわゆるスパム攻撃が激しくなっています。</p>
+            <p>特に流行のblogなどで被害が顕著ですが、同じように受信出来てしまうtDiaryでも同様のことが起こっています（対抗策が十分でない分、被害を受けやすくなっています）</p>
+            <p>そこで、tDiaryの持つフィルタ機能とプラグイン機能を利用して、これらのスパムのうちコメントスパムに対して対策を行うのが「コメントキーフィルタ＆プラグイン」です。自動的にスパム送信を行うプログラムに対し効果を発揮します。</p>
+            <p>尚、本フィルタ＆プラグインは<a href="http://sheepman.parfait.ne.jp/20040918.html#p02">sheepman発案のコメントスパム対策</a>をフィルタとプラグインのみで実現したものです。</p>
+          </div>
+        </div>
+
+        <div class="section">
+          <h3><a name="system">動作環境</a></h3>
+          <div class="secbody">
+            <p>動作に必要な条件は以下の通りです。</p>
+            <ul>
+              <li>tDiary 2.0.0以上のシステム</li>
+              <li>自前でプラグイン及びフィルタがインストール出来る</li>
+            </ul>
+          </div>
+        </div>
+
+        <div class="section">
+          <h3><a name="install">インストール・アンインストール</a></h3>
+          <div class="secbody">
+            <ul>
+              <li>key.rb を tdiary/filter 下に配置します。</li>
+              <li>comment_key.rb を misc/plugin 下に、 ja/cooment_key.rb を misc/plugin/ja 下に配置します。</li>
+              <li>tDiary の設定ページにアクセスし、「プラグイン選択」にて comment_key.rb を有効にしてください。</li>
+            </ul>
+            <p>これでインストールは完了です。フィルタとプラグインの両方をインストールしないと動作しませんので注意してください。</p>
+          </div>
+        </div>
+
+        <div class="section">
+          <h3><a name="usage">使い方</a></h3>
+          <div class="secbody">
+            <p>インストールが完了した時点で、設定ページのプラグインメニューに「コメントキーフィルタ」が表示されます。このページにまずは移動してください。</p>
+            <p>このページの「コメントキーフィルタを有効にする」のチェックを有効にすることで、とりあえずフィルタが有効になります。</p>
+            <p>その下にはキーの値を生成する際に使用する文字列を指定します。この文字列がキーの値の元になる文字列の一部となりますので、お好きな文字列を設定してください。<strong>この文字列が他の人の日記のものと同一だと、生成されるキーはやはり同一になってしまうのでご注意を。</strong></p>
+            <p>その下にある「常に同一の鍵文字列を生成する」は通常は必要ありませんのでそのままにしておいてください。</p>
+            <p>以上が設定し終わると、このフィルタが機能します。</p>
+          </div>
+        </div>
+
+        <div class="section">
+          <h3><a name="algorithm">動作原理</a></h3>
+          <div class="secbody">
+            <p>本フィルタは<a href="http://sheepman.parfait.ne.jp/20040918.html#p02">sheepmanさんのコメントスパム対策</a>を移植したものなので原理は同じですが、一応説明しておきます。</p>
+            <p>本プラグイン＆フィルタはコメントフォームに対し、値として日付ごとに一意な文字列を持つキー "comment_key" を追加し<span class="footnote"><a href="#fn01">*1</a></span>、コメント書き込み時にこのキーとフィルタ内で生成した文字列を比較することで「日記表示時にそのページにあったコメントフォームから書き込んだのか？」を判定します。</p>
+            <p>判定がNGとなった場合はコメントの保存を行いません。</p>
+            <p>キーに設定された文字列は、元となるキー文字列のMD5チェックサムになっていて、元となるキー文字列を容易に推測されないようになっています。</p>
+            <div class="footnote">
+              <p class="footnote"><a name="fn01">*1</a>&nbsp;日付に関係なく同一の文字列を生成することも出来ますが突破されやすくなります。詳しくは<a href="#faq">FAQ</a>を参照</p>
+            </div>
+          </div>
+        </div>
+
+        <div class="section">
+          <h3><a name="faq">FAQ</a></h3>
+          <div class="secbody">
+            <dl>
+              <dt>Q1. 最新表示でコメントできない</dt>
+              <dd>
+                <p>もしかして、speed_comment.rbプラグインを使っていませんか？その場合、この本プラグイン＆フィルタではコメント対象の日付が特定できません。その時は設定ページにある「常に同一の鍵文字列を生成する」のチェックを入れれば一応コメントできるようになります。</p>
+                <p>ただし、最新表示のみならず日付別表示でも常に同じ鍵文字列を生成するので多少コメントスパムの突破率は上がると思います。</p>
+              </dd>
+            </dl>
+          </div>
+        </div>
+
+        <div class="section">
+          <h3><a name="sorry">おことわり</a></h3>
+          <div class="secbody">
+            <p>このフィルタ＆プラグインはコメントスパムを防止する目的で作成されていますが、これを完全に防ぐことはできませんのでご了承ください。</p>
+            <p>例：　表示された日記のコメントフォームを解析したうえでスパム送信を行うようなプログラムなど</p>
+          </div>
+        </div>
+
+        <div class="section">
+          <h3><a name="license">ライセンスなど</a></h3>
+          <div class="secbody">
+            <p>「コメントキーフィルタ＆プラグイン」はGPLライセンスに従い公開・配布されるものです。</p>
+          </div>
+        </div>
+
+        <div class="section">
+          <h3><a name="thanks">謝辞</a></h3>
+          <div class="secbody">
+            <p>このフィルタ＆プラグインは、コメントスパムに困っていた時に見つけた<a href="http://sheepman.parfait.ne.jp/20040918.html#p02">sheepmanさんのコメントスパム対策</a>を使いたいんだけど、本体に手を入れたくない。なんとかならないか…と考えて出来たものです。重要な部分はほぼそのまま使わせてもらっています。ありがとうございます。</p>
+            <p>これでなんとかコメントスパムが減ればいいな…。</p>
+          </div>
+        </div>
+
+        <div class="section">
+          <h3><a name="changelog">履歴</a></h3>
+          <div class="secbody">
+            <dl>
+              <dt>2005/08/31 (Ver.0.5.0)</dt>
+              <dd>設定ページのカテゴリに対応(tDiary-2.1.2.20050826以降)</dd>
+              <dt>2005/04/17 (Ver.0.4.0)</dt>
+              <dd>常に同一の鍵文字列を生成するオプションを追加</dd>
+              <dt>2005/03/21 (Ver.0.3.0)</dt>
+              <dd>携帯からコメントが出来なくなっていたのを修正</dd>
+              <dt>2005/01/25 (Ver.0.2.0)</dt>
+              <dd>TrackBack及びPingbackの投稿にも適用されてしまっていたのを修正</dd>
+              <dt>2005/01/15 (Ver.0.1.0)</dt>
+              <dd>初版公開</dd>
+            </dl>
+          </div>
+        </div>
+
+        <div class="section">
+          <h3><a name="prevver">過去のバージョン</a></h3>
+          <div class="secbody">
+            <ul>
+              <li><a href="./comment_key-0.4.0.tar.gz">Ver 0.4.0</a></li>
+              <li><a href="./comment_key-0.3.0.tar.gz">Ver 0.3.0</a></li>
+              <li><a href="./comment_key-0.2.0.tar.gz">Ver 0.2.0</a></li>
+              <li><a href="./comment_key-0.1.0.tar.gz">Ver 0.1.0</a></li>
+            </ul>
+          </div>
+        </div>
+
+      </div>
+
+    </div>
+
+    <div id="footerArea">
+      <address>Copyright &copy; ハハハハ 2001-2005 All Right Reserved.</address>
+      <address>E-Mail: rin_ne-at-big.or.jp</address>
+
+      <address class="lm">
+        <script type="text/javascript">
+          <!--
+            document.write("Last Modified: ", document.lastModified);
+          //-->
+        </script>
+      </address>
+    </div>
+  </body>
+</html>
Index: /tdiary/branches/upstream/contrib/doc/ja/image_gps.txt
===================================================================
--- /tdiary/branches/upstream/contrib/doc/ja/image_gps.txt (revision 710)
+++ /tdiary/branches/upstream/contrib/doc/ja/image_gps.txt (revision 710)
@@ -0,0 +1,22 @@
+
+image_gps.rb
+
+rexif_gps.rbおよびrjpeg.rbは
+rexif(http://www.koka-in.org/~zophos/lib/rexif)
+をもとに作成しました。
+
+wgs2tky.rbはWGS84からTokyoへの変換
+(http://homepage3.nifty.com/Nowral/02_DATUM/02_DATUM.html#WGS2TKY)
+を参考にしました。
+
+image_gps.rbはimage.rbおよび、
+mapion.rb(http://tdiary-users.sourceforge.jp/cgi-bin/wiki.cgi?mapion%2Erb)
+をもとに作成しました。
+
+----
+
+このパッケージに含まれるimage_gps.rbは、旧バージョンです。
+より高機能になった最新版は以下のURLから入手できます(ただし導入方法が少し
+難しくなっています):
+
+http://mmho.no-ip.org/hiki/hiki.cgi?image_gps.rb
Index: /tdiary/branches/upstream/contrib/doc/ja/opensearch_ad.txt
===================================================================
--- /tdiary/branches/upstream/contrib/doc/ja/opensearch_ad.txt (revision 710)
+++ /tdiary/branches/upstream/contrib/doc/ja/opensearch_ad.txt (revision 710)
@@ -0,0 +1,16 @@
+! 名前
+OpenSearch Auto-Discovery プラグイン
+
+! 概要
+tDiaryをOpenSearch Auto-Discoveryに対応させるプラグイン
+
+!使い方
+利用するには OpenSearch description XML をあらかじめ作成する必要があります。
+サンプルファイルは http://www.opensearch.org/Specifications/OpenSearch/1.1 にあります。
+
+!ライセンス
+GPL2
+
+!更新履歴
+!!20070117
+* tDiary-contrib に追加
Index: /tdiary/branches/upstream/contrib/doc/ja/README.txt
===================================================================
--- /tdiary/branches/upstream/contrib/doc/ja/README.txt (revision 710)
+++ /tdiary/branches/upstream/contrib/doc/ja/README.txt (revision 710)
@@ -0,0 +1,12 @@
+== tDiary contrib パッケージ ==
+
+このパッケージには、tDiaryに関係したいくつかのユーティリティやプラグ
+インが収録されています。ドキュメントはそれぞれのツールのディレクトリ
+に含まれています。
+
+著作権およびライセンスは各テーマのファイルに記述してあるものに従いま
+すが、特に明記されていない場合、配布条件はGPL2、著作権は以下になりま
+す。
+ 
+   Copyright (c) 2005 TADA Tadashi <sho@spc.gr.jp> 
+ 
Index: /tdiary/branches/upstream/contrib/doc/ja/account_ad.txt
===================================================================
--- /tdiary/branches/upstream/contrib/doc/ja/account_ad.txt (revision 710)
+++ /tdiary/branches/upstream/contrib/doc/ja/account_ad.txt (revision 710)
@@ -0,0 +1,39 @@
+! 概要
+tDiaryのヘッダに[[Account Auto-Discovery|http://d.hatena.ne.jp/naoya/20050729/1122598831]]（旧[[Hatena ID Auto-Discovery|http://d.hatena.ne.jp/naoya/20050722/1121993475]]）を埋め込みます。
+
+! 使い方
+tDiaryのプラグインフォルダに入れるだけです。インストール後、プラグイン選択からaccount_ad.rbを選択し、設定画面からAccount NameにはてなIDを設定してください。
+
+! 更新履歴
+!! 20080120
+* openid.rb をベースにリファクタリング
+
+!! 20070109
+* サニタイズキャンペーンに伴う修正
+
+!! 20050812
+* add_head_procにブロック引数を渡していたので削除
+* account_nameが未設定のときはRDF埋め込みを抑制
+
+!! 20050810
+* dayモード時にはpermalinkを表示するように変更
+* 設定画面を微調整
+* 名前をaccount_id.rbからaccount_ad.rbに変更
+* 名前をhatena_id.rbからaccount_id.rbに変更
+* アカウントサービスを変更できるように設定。デフォルトはhttp://www.hatena.ne.jp/
+
+!! 20050809
+* [[Account Auto-Discovery を dc:creator から foaf:maker へ|http://d.hatena.ne.jp/naoya/20050804/1123142579]]の仕様変更に対応
+
+!! 20050725
+* [[RFC: 続・Hatena ID Auto-Discovery の仕様|http://d.hatena.ne.jp/naoya/20050725/1122277102]]の仕様変更に対応
+
+!! 20050723
+* サニタイズ漏れを修正
+* 初期化の修正
+
+!! 20050722
+* 初版公開
+
+! ライセンス
+GPL
Index: /tdiary/branches/upstream/contrib/filter/rblcheck.rb
===================================================================
--- /tdiary/branches/upstream/contrib/filter/rblcheck.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/filter/rblcheck.rb (revision 710)
@@ -0,0 +1,36 @@
+# rblcheck.rb
+# Copyright (c) 2004 MoonWolf <moonwolf@moonwolf.com>
+# Distributed under the GPL
+#
+# options:
+#   @options['rblcheck.list'] = [
+#     'bl.moonwollf.com', # RBL list
+#   ]
+require 'socket'
+
+module TDiary
+  module Filter
+    class RblcheckFilter < Filter
+      alias :_filter :referer_filter
+      alias :_filter :comment_filter
+
+      private
+
+      def _filter( *args )
+        rev_addr = @cgi.remote_addr.split(".").reverse.join(".")
+        @conf['rblcheck.list'].each {|rbl|
+          addr = "#{rev_addr}.#{rbl}"
+          begin
+            host = Socket.getaddrinfo(addr, "http")[0][3]
+            case host
+            when "127.0.0.2"
+              return false
+            end
+          rescue SocketError
+          end
+        }
+        true
+      end
+    end
+  end
+end
Index: /tdiary/branches/upstream/contrib/filter/spambayes.rb
===================================================================
--- /tdiary/branches/upstream/contrib/filter/spambayes.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/filter/spambayes.rb (revision 710)
@@ -0,0 +1,510 @@
+# Copyright (C) 2007, KURODA Hiraku <hiraku@hinet.mydns.jp>
+# You can redistribute it and/or modify it under GPL2. 
+
+require "bayes"
+require "uri"
+
+module TDiary::Filter
+	class SpambayesFilter < Filter
+		class TokenList < Bayes::TokenList
+			def initialize
+				super(Bayes::CHARSET::EUC)
+			end
+		end
+
+		module Misc
+			@@without_filtering = nil
+			@@force_filtering = nil
+			def without_filtering?
+				@@without_filtering || (@conf[conf_use]||"").size==0
+			end
+			def without_filtering
+				orig = @@without_filtering
+				@@without_filtering = true
+				yield
+			ensure
+				@@without_filtering = orig
+			end
+			def force_filtering?; @@force_filtering; end
+			def force_filtering
+				orig = @@force_filtering
+				@@force_filtering = true
+				yield
+			ensure
+				@@force_filtering = orig
+			end
+
+			@@conf = nil
+			def self.conf=(conf)
+				@@conf ||= conf
+			end
+			def self.conf; @@conf; end
+			def self.to_native(s)
+				s ? @@conf.to_native(CGI.unescape(s)).gsub(/[\x00-\x20]/, " ") : ""
+			end
+
+			PREFIX = "spambayes"
+			def conf_filter; "#{PREFIX}.filter"; end
+			def conf_mail; "#{PREFIX}.mail"; end
+			def conf_threshold; "#{PREFIX}.threshold"; end
+			def conf_use; "#{PREFIX}.use"; end
+			def conf_log; "#{PREFIX}.log"; end
+			def conf_for_referer; "#{PREFIX}.for_referer"; end
+
+			def cache_path
+				@conf.cache_path || "#{@conf.data_path}cache"
+			end
+
+			def bayes_cache
+				Dir.mkdir(cache_path) unless File.exist?(cache_path)
+				r = "#{cache_path}/bayes"
+				Dir.mkdir(r) unless File.exist?(r)
+				r
+			end
+
+			def referer_cache(key)
+				"#{bayes_cache}/referer_#{key}.log"
+			end
+
+			def referer_corpus
+				"#{corpus_path}/referer.db"
+			end
+
+			def unescape_referer(referer)
+				@conf.to_native(CGI.unescape(referer)).gsub(/[\x00-\x20]+/, " ")
+			end
+
+			def debug_log
+				"#{bayes_cache}/debug.log"
+			end
+
+			def debug(*args)
+				open(debug_log, "a") do |f|
+					f.puts(*args)
+				end
+			end
+
+			def corpus_path
+				r = "#{bayes_cache}/corpus"
+				Dir.mkdir(r) unless File.exist?(r)
+				r
+			end
+
+			def bayes_db
+				 "#{@conf.data_path}/bayes.db"
+			end
+
+			def bayes_filter(reset=false)
+				if reset
+					@bayes_filter = nil
+					File.delete(bayes_db) if File.exist?(bayes_db)
+				end
+
+				case @conf[conf_filter]
+				when /graham/i
+					@bayes_filter ||= Bayes::PaulGraham.new(bayes_db)
+				else
+					@bayes_filter ||= Bayes::PlainBayes.new(bayes_db)
+				end
+				@bayes_filter
+			end
+
+			def threshold
+				(@conf[conf_threshold]||"0.95").to_f
+			end
+
+			def url(path=nil)
+				if /^https?:\/\// =~ (path||"")
+					path
+				else
+					@conf.base_url.sub(/\/*$/, '/') + (path||'')
+				end
+			end
+
+			def url2(path=nil)
+				if path && URI.parse(path).absolute?
+					path
+				else
+					base = URI.parse @conf.base_url
+					base.path = base.path.sub(%r{/*$}, '/') + (path || '')
+					base.to_s
+				end
+			end
+
+			def index_url
+				url(@conf.index)
+			end
+
+			def update_url
+				url(@conf.update)
+			end
+		end
+
+		class Comment
+			attr_reader :name, :date, :mail, :body, :remote_addr, :diary_date
+
+			def self.load(file_name)
+				r = nil
+				open(file_name) do |f|
+					f.flock(File::LOCK_SH)
+					r = Marshal.load(f)
+				end
+				raise "NoData" unless r.is_a?(self)
+				r
+			end
+
+			def initialize(comment, cgi)
+				@name = comment.name || ""
+				@date = comment.date || Time.now
+				@mail = comment.mail || ""
+				@body = comment.body || ""
+				@remote_addr = cgi.remote_addr || ""
+				d = cgi.params['date'][0] || Time.now.strftime("%Y%m%d")
+				@diary_date = Time::local(*d.scan(/^(\d{4})(\d{2})(\d{2})$/)[0]) + 12*60*60
+			end
+
+			def digest
+				Digest::MD5.hexdigest([@name, @date, @mail, @body, @remote_addr, @diary_date].join)
+			end
+
+			def token
+				r = TokenList.new
+
+				if @name.empty?
+					r.push("", "N")
+				else
+					r.add_message(@name, "N")
+				end
+				r.add_mail_addr(@mail, "M")
+				b = @body.dup
+				URI.extract(b, %w[http https ftp]) do |url|
+					r.add_url(url, "U")
+				end
+				r.add_message(b)
+				r.add_host(@remote_addr, "A")
+
+				r
+			end
+
+			def cache_name
+				@date.strftime("%Y%m%d%H%M%S")+digest
+			end
+		end
+
+		class Referer
+			@@specials = {}
+
+			def self.load_list(f)
+				r = []
+				open(f) do |f|
+					f.flock(File::LOCK_SH)
+					r << Marshal.load(f) until f.eof?
+				end
+				r
+			rescue
+				[]
+			end
+
+			def self.truncate_list(fn, size)
+				return unless File.exist?(fn)
+				open(fn, "a+") do |f|
+					f.flock(File::LOCK_EX)
+
+					buff = []
+					buff << Marshal.load(f) until f.eof?
+
+					buff.slice!(0, size)
+					f.truncate(0)
+					buff.each do |i|
+						Marshal.dump(i, f)
+					end
+				end
+			end
+
+			def self.from_link(link)
+				if /^(.*?)_(.*)$/=~link
+					addr = $1
+					url = $2
+					new(CGI.unescape(url), addr ? CGI.unescape(addr) : nil)
+				end
+			end
+
+			def self.from_html(html)
+				if /^(.*?)_(.*)$/=~html
+					addr = $1
+					url = $2
+					new(CGI.unescapeHTML(url), addr ? CGI.unescapeHTML(addr) : nil)
+				end
+			end
+
+			attr_reader :referer, :remote_addr
+			def initialize(referer, remote_addr = nil)
+				@referer = referer
+				@remote_addr = remote_addr
+			end
+
+			def hash
+				@referer.hash
+			end
+
+			def eql?(dst)
+				(self.class == dst.class) and (@referer == dst.referer)
+			end
+
+			def to_s
+				Misc.to_native(@referer)
+			end
+
+			def to_html
+				CGI.escapeHTML(@remote_addr||"") + "_" + CGI.escapeHTML(@referer)
+			end
+
+			def to_link
+				CGI.escape(@remote_addr||"") + "_" + CGI.escape(@referer)
+			end
+
+			def <=>(o)
+				to_s <=> o.to_s
+			end
+
+			def split_url
+				begin
+					url = URI.parse(@referer)
+					query    = url.query
+					fragment = url.fragment
+					url.query    = nil
+					url.fragment = nil
+					base = url.to_s
+				rescue
+					base, query, fragment = @referer.scan(/^(.*?)(?:\?([^#]*?)(?:#(.*))?)?$/)[0]
+				end
+				[base, query, fragment]
+			end
+
+			def token
+				if l=special?
+					m = l+"_token"
+					if respond_to?(m)
+						r = send(m)
+					else
+						r = special_token(@@specials[l])
+					end
+				else
+					r = TokenList.new
+
+					base, request, anchor = split_url
+					r.add_url(base, "R")
+					r.add_message(Misc.to_native(request)) if request
+					r.add_message(Misc.to_native(anchor)) if anchor
+				end
+
+				r.add_host(@remote_addr, "A") if @remote_addr
+				r
+			end
+
+			def viewable_html
+				if l=special?
+					m = l+"_html"
+					if respond_to?(m)
+						r = send(m)
+					else
+						r = special_html(@@specials[l])
+					end
+				else
+					r = to_s
+				end
+				CGI.escapeHTML(r||"")
+			end
+
+			def special?
+				r = @@specials.find do |n, a|
+					a[0] =~ @referer
+				end
+				r ? r[0] : false
+			end
+
+			def self.special(name, regexp, label = nil)
+				name = name.to_s
+				label ||= name.capitalize
+				@@specials[name.to_s] = [regexp, label]
+			end
+
+			def special_html(special)
+				re, label = special
+				"#{label}: " + Misc.to_native(@referer[re, 1])
+			end
+
+			def special_token(special)
+				re = special[0]
+				r = TokenList.new
+				r.add_message(Misc.to_native(@referer[re, 1]))
+				r
+			end
+
+			RE_QUERY_SEP = /[&;]|$/
+			RE_QUERY_HEAD = /\?(?:.*#{RE_QUERY_SEP})?/o
+
+			RE_GOOGLE_HOSTS = /.*\.google\.(?:(?:co\.)?[a-z]{2}|com(?:\.[a-z]{2})?)/o
+			RE_GOOGLE = %r[^https?://#{RE_GOOGLE_HOSTS}/.*#{RE_QUERY_HEAD}(?:as_)?q=(.*?)#{RE_QUERY_SEP}]o
+			special :google, RE_GOOGLE
+
+			RE_GOOGLE_IP = /209\.85\.\d{3}\.\d{1,3}|72\.14\.\d{3}\.\d{1,3}/
+			RE_GOOGLE_CACHE = %r[^https?://#{RE_GOOGLE_IP}/search#{RE_QUERY_HEAD}q=cache:[^:]+:(.*?)(?:(?:\+|\s+)(.*?))?#{RE_QUERY_SEP}]o
+			special :google_cache, RE_GOOGLE_CACHE
+			def google_cache_token
+				r = TokenList.new
+				RE_GOOGLE_CACHE =~ @referer
+				ref = "http://#{CGI.unescape($1)}"
+				words = $2
+				r.add_url(ref, "R")
+				r.add_message(Misc.to_native(words))
+			end
+
+			def google_cache_html
+				RE_GOOGLE_CACHE =~ @referer
+				ref = "http://#{CGI.unescape($1)}"
+				words = $2
+				"Google(Cache): #{ref} #{Misc.to_native(words)}"
+			end
+
+			RE_EZ_GOOGLE = %r[^https?://ezsch\.ezweb\.ne\.jp/search/ezGoogleMain\.php#{RE_QUERY_HEAD}query=(.*?)#{RE_QUERY_SEP}]o
+			special :ez_google, RE_EZ_GOOGLE, "Google(ezweb)"
+
+			RE_EZWEB = %r[^https?://ezsch\.ezweb\.ne\.jp/.*?#{RE_QUERY_HEAD}query=(.*?)#{RE_QUERY_SEP}]
+			special :ezweb, RE_EZWEB, "EZweb"
+
+			RE_GOO = %r[^http://search\.goo\.ne\.jp/.*?#{RE_QUERY_HEAD}MT=(.*?)#{RE_QUERY_SEP}]o
+			special :goo, RE_GOO
+
+			RE_NIFTY = %r[^https?://search\.nifty\.com/.*?#{RE_QUERY_HEAD}Text=(.*?)#{RE_QUERY_SEP}]o
+			special :nifty, RE_NIFTY
+
+			RE_LIVESEARCH = %r[^https?://search\.live\.com/.*?#{RE_QUERY_HEAD}q=(.*?)#{RE_QUERY_SEP}]o
+			special :livesearch, RE_LIVESEARCH, "Live Search"
+
+			RE_BIGLOBE = %r[^https?://.*search\.biglobe\.ne\.jp/.*?#{RE_QUERY_HEAD}q=(.*?)#{RE_QUERY_SEP}]o
+			special :biglobe, RE_BIGLOBE
+
+			RE_MSN = %r[^https?://search\.msn\.co\.jp/.*?#{RE_QUERY_HEAD}q=(.*?)#{RE_QUERY_SEP}]o
+			special :msn, RE_MSN, "MSN"
+
+			RE_INFOSEEK = %r[^https?://search\.www\.infoseek\.co\.jp/.*?#{RE_QUERY_HEAD}qt=(.*?)#{RE_QUERY_SEP}]o
+			special :infoseek, RE_INFOSEEK
+
+			RE_HATENA_B = %r[^https?://b\.hatena\.ne\.jp/[^/]+/(.*?)(?:\?.*)?$]o
+			special :hatena_b, RE_HATENA_B, "Hatena::Bookmark"
+
+			RE_YAHOO = %r[^https?://.*\.yahoo\.co(?:m|\.[a-z]{2})/.*?#{RE_QUERY_HEAD}p=(.*?)#{RE_QUERY_SEP}]o
+			special :yahoo, RE_YAHOO
+
+			RE_BAIDU = %r[^https?://.*\.baidu\.jp/.*?#{RE_QUERY_HEAD}wd=(.*?)#{RE_QUERY_SEP}]o
+			special :baidu, RE_BAIDU
+		end
+
+		include Misc
+
+		def initialize( cgi, conf )
+			super
+
+			Misc.conf = conf
+		end
+
+		def comment_filter(diary, comment)
+			return false if force_filtering?
+			return true if without_filtering?
+			r = true
+			data = Comment.new(comment, @cgi)
+
+			base_url = "#{update_url}?conf=spambayes;mode=conf;sb_mode="
+			spam_url = "Register as spam : #{base_url}confirm_spam"
+			ham_url = "Register as ham : #{base_url}confirm_ham"
+
+			e = bayes_filter.estimate(data.token)
+			case
+			when e == nil
+				r = false
+				tag = "DOUBT"
+				url = "#{spam_url}\n#{ham_url}"
+			when e>threshold
+				r = false
+				tag = "SPAM"
+				url = ham_url
+			else
+				r = true
+				tag = "HAM"
+				url = spam_url
+			end
+			cn = tag[0,1]+data.cache_name
+			open("#{bayes_cache}/#{cn}", "w") do |f|
+				f.flock(File::LOCK_SH)
+				f.rewind
+				Marshal.dump(data, f)
+			end
+			url.gsub!(/(\n|\z)/){";comment_id=#{cn}#$1"}
+
+			require "socket"
+			require "time"
+			subject = "#{tag}:#{data.body.gsub(/\n/, " ")}".scan(/.{1,8}/e).map{|b| @conf.to_mail(b)}
+			subject = subject.map{|i| "=?ISO-2022-JP?B?"+[i].pack("m").chomp+"?="}.join("\n ")
+			addr = @conf[conf_mail]
+			body = <<EOT
+From: BayesFilter <#{addr}>
+To: #{addr}
+Date: #{Time.now.rfc2822}
+Message-Id: <bayesfilter_#{cn}@#{Socket::gethostname}>
+Subject: #{subject}
+MIME-Version: 1.0
+Content-Type: text/plain; charset="iso-2022-jp"
+Content-Transfer-Encoding: 7bit
+Errors-To: #{addr}
+X-Mailer: tDiary #{TDIARY_VERSION}
+X-URL: http://www.tdiary.org/
+
+Filter treated comment as #{tag}.
+#{url}
+----
+Target: #{index_url}/?date=#{data.diary_date.strftime("%Y%m%d")}
+Name: #{data.name}
+Mail: #{data.mail}
+IP : #{data.remote_addr}
+Body:------
+#{data.body.scan(/.{1,40}/).map{|l| @conf.to_mail(l)}.join("\n")}
+EOT
+			begin
+				plugin = TDiary::Plugin.new("conf"=>@conf, "mode"=>"comment", "diaries"=>nil, "cgi"=>@cgi, "years"=>nil, "cache_path"=>cache_path, "date"=>data.diary_date, "comment"=>comment, "last_modified"=>nil)
+				plugin.comment_mail(body, addr) if /^.*@.*/ =~ addr
+			rescue ArgumentError
+			end
+			r
+		rescue Exception => e
+			debug "---- comment_filter ----", Time.now, e.message, e.class.name, e.backtrace.join("\n")
+			r
+		end
+
+		def referer_filter(referer)
+			return true if without_filtering? || !(@conf[conf_for_referer])
+			r = true
+			referer = Referer.new(referer, ENV["REMOTE_ADDR"])
+			token = referer.token
+			e = bayes_filter.estimate(token)
+			case
+			when e==nil
+				r = false
+				key = "doubt"
+			when e>threshold
+				r = false
+				key = "spam"
+			else
+				key = "ham"
+			end
+			open(referer_cache(key), "a") do |f|
+				f.flock(File::LOCK_SH)
+				Marshal.dump(referer, f)
+			end
+			r
+		rescue Exception => e
+			debug "---- referer_filter ----", Time.now, e.message, e.class.name, e.backtrace.join("\n")
+			raise
+		end
+	end
+end
Index: /tdiary/branches/upstream/contrib/filter/antirefspam.rb
===================================================================
--- /tdiary/branches/upstream/contrib/filter/antirefspam.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/filter/antirefspam.rb (revision 710)
@@ -0,0 +1,263 @@
+#
+# antirefspam.rb
+#
+# Copyright (c) 2004-2005 T.Shimomura <redbug@netlife.gr.jp>
+# You can redistribute it and/or modify it under GPL2.
+# Please use version 1.0.0 (not 1.0.0G) if GPL doesn't want to be forced on me.
+#
+
+require 'net/http'
+require 'uri'
+
+module TDiary
+  module Filter
+
+    class AntirefspamFilter < Filter
+      # 有効にすると指定したファイルにデバッグ情報文字列を追記する
+      def debug_out(filename, str)
+        if $debug
+          filename = File.join(@conf.data_path,"AntiRefSpamFilter",filename)
+          File::open(filename, "a+") {|f|
+            f.puts str
+          }
+        end
+      end
+
+      # str に指定された文字列が適切なリンク先を含んでいるかをチェック
+      def isIncludeMyUrl(str)
+        # str に日記のURLが含まれているかどうか
+        base_url = @conf.base_url
+        unless base_url.empty?
+          if str.include? base_url
+            return true
+          end
+        end
+
+        # str にトップページURLが含まれているかどうか
+        unless @conf.index_page.empty?
+          if @conf.index_page.index(URI.regexp(%w[http https])) == 0
+            if str.include? @conf.index_page
+              return true
+            end
+          end
+        end
+
+        # str に許容するリンク先が含まれているかどうか
+        if (myurl = @conf['antirefspam.myurl']) && !myurl.empty?
+          if str.include? myurl
+            return true
+          end
+
+          #url = myurl.gsub("/", "\\/").gsub(":", "\\:")
+          #exp = Regexp.new(url)
+          exp = Regexp.union(myurl)
+          if exp =~ str
+            return true
+          end
+        end
+
+        return false
+      end
+
+      def referer_filter(referer)
+        conf_disable = @conf['antirefspam.disable'] != nil ? @conf['antirefspam.disable'].to_s : ''
+        conf_checkreftable = @conf['antirefspam.checkreftable'] != nil ? @conf['antirefspam.checkreftable'].to_s : ''
+        conf_trustedurl = @conf['antirefspam.trustedurl'] != nil ? @conf['antirefspam.trustedurl'].to_s : ''
+        conf_proxy_server = @conf['antirefspam.proxy_server'] != nil && @conf['antirefspam.proxy_server'].size > 0 ? @conf['antirefspam.proxy_server'].to_s : nil
+        conf_proxy_port = @conf['antirefspam.proxy_port'] != nil && @conf['antirefspam.proxy_port'].size > 0 ? @conf['antirefspam.proxy_port'].to_s : nil
+
+        if conf_disable == 'true'  or    # リンク元チェックが有効ではない場合はスルーする
+           referer == nil          or    # リンク元が無い
+           referer.size <= 1       or    # 一部のアンテナで更新時刻が取れなくなる問題に対応するため、リンク元が１文字以内の場合は許容
+           isIncludeMyUrl(referer)       # 自分の日記内からのリンクは信頼する
+        then
+          return true
+        end
+
+        # "信頼できるURL" を１つずつ取り出してrefererと合致するかチェックする
+        conf_trustedurl.each_line do |trusted|
+          trusted.sub!(/\r?\n|\r/,'')
+          next if trusted =~ /\A(\#|\s*)\z/  # #または空白で始まる行は読み飛ばす
+
+          # まずは "信頼できる URL" が referer に含まれるかどうか
+          if referer.include? trusted
+            debug_out("trusted", trusted+" (include?) "+referer)
+            return true
+          end
+
+          # 含まれなかった場合は "信頼できる URL" を正規表現とみなして再チェック
+          begin
+            #if referer =~ Regexp.new( trusted.gsub("/", "\\/").gsub(":", "\\:") )
+            if referer =~ Regexp.union( trusted )
+              debug_out("trusted", trusted+" (=~) "+referer)
+              return true
+            end
+          rescue
+            debug_out("error_config", "trustedurl: "+trusted)
+          end
+        end
+
+        # URL置換リストを見る
+        if conf_checkreftable == 'true'
+          # "URL置換リスト" を１つずつ取り出してrefererと合致するかチェックする
+          @conf.referer_table.each do |url, name|
+            begin
+              if /#{url}/i =~ referer && url != '^(.{50}).*$'
+                debug_out("trusted", url+" (=~referer_table)  "+referer)
+                return true
+              end
+            rescue
+              debug_out("error_config", "referer_table: "+url)
+            end
+          end
+        end
+
+        @work_path = File.join(@conf.data_path,"AntiRefSpamFilter")
+        @spamurl_list = File.join(@work_path,"spamurls")  # referer spam のリンク元一覧
+        @spamip_list  = File.join(@work_path,"spamips")   # referer spam のIP一覧
+        @safeurl_list = File.join(@work_path,"safeurls")  # おそらくは問題のないリンク元一覧
+
+        # ディレクトリ/ファイルが存在しなければ作る
+        unless File.exist? @work_path
+          Dir::mkdir(@work_path)
+        end
+        unless File.exist? @spamurl_list
+          File::open(@spamurl_list, "a").close
+        end
+        unless File.exist? @safeurl_list
+          File::open(@safeurl_list, "a").close
+        end
+
+        uri = URI.parse(referer)
+        temp_filename = File.join(@work_path,uri.host)
+        # チェック時には対象のドメイン名を持った一時ファイルを作る
+        begin
+          File::open(temp_filename, File::RDONLY | File::CREAT | File::EXCL).close
+
+          # 一度 SPAM URL とみなしたら以後は以後は拒否
+          spamurls = IO::readlines(@spamurl_list).map {|url| url.chomp }
+          if spamurls.include? referer
+            return false
+          end
+
+          # 一度 SPAM URL でないと判断したら以後は許可
+          safeurls = IO::readlines(@safeurl_list).map {|url| url.chomp }
+          if safeurls.include? referer
+            return true
+          end
+
+          # リンク元 URL の HTML を引っ張ってくる
+          Net::HTTP.version_1_2   # おまじないらしい
+          body = ""
+          begin
+            Net::HTTP::Proxy(conf_proxy_server, conf_proxy_port).start(uri.host, uri.port) do |http|
+              if uri.path == ""
+                response, = http.get("/")
+              else
+                response, = http.get(uri.request_uri)
+              end
+              body = response.body
+            end
+
+            # body に日記の URL が含まれていなければ SPAM とみなす
+            unless isIncludeMyUrl(body)
+              File::open(@spamurl_list, "a+") {|f|
+                f.puts referer
+              }
+              File::open(@spamip_list, "a+") {|f|
+                f.puts [@cgi.remote_addr, Time.now.utc.strftime("%Y/%m/%d %H:%M:%S UTC")].join("\t")
+              }
+              return false
+            else
+              File::open(@safeurl_list, "a+") {|f|
+                f.puts referer
+              }
+            end
+          rescue
+            # エラーが出た場合は @spamurl_list に入れない＆リンク元にも入れない
+            return false
+          end
+
+        rescue StandardError, TimeoutError
+          # 現在チェック中なら、今回はリンク元に勘定しない
+          return false
+        ensure
+          begin
+            File::delete(temp_filename)
+          rescue
+          end
+        end
+
+        return true
+      end
+
+
+
+      def log_spamcomment( diary, comment )
+        @work_path = File.join(@conf.data_path,"AntiRefSpamFilter")
+        @spamcomment_list = File.join(@work_path,"spamcomments")  # comment spam の一覧
+
+        # ディレクトリ/ファイルが存在しなければ作る
+        unless File.exist? @work_path
+          Dir::mkdir(@work_path)
+        end
+        unless File.exist? @spamcomment_list
+          File::open(@spamcomment_list, "a").close
+        end
+
+        File::open(@spamcomment_list, "a+") {|f|
+          f.puts "From: "+comment.name+" <"+comment.mail+">"
+          f.puts "To: "+diary.date.to_s
+          f.puts "Date: "+comment.date.to_s
+          f.puts comment.body
+          f.puts ".\n\n"
+        }
+      end
+
+      def comment_filter( diary, comment )
+        # ツッコミに日本語(ひらがな/カタカナ)が含まれていなければ不許可
+        if @conf['antirefspam.comment_kanaonly'] != nil
+          if @conf['antirefspam.comment_kanaonly'].to_s == 'true'
+            unless comment.body =~ /[ぁ-んァ-ヴー]/
+              log_spamcomment( diary, comment )
+              return false
+            end
+          end
+        end
+
+        # ツッコミの文字数が指定した上限以内でないなら不許可
+        maxsize = @conf['antirefspam.comment_maxsize'].to_i
+        if maxsize > 0
+          unless comment.body.size <= maxsize
+            log_spamcomment( comment )
+            return false
+          end
+        end
+
+        # NGワードが１つでも含まれていたら不許可
+        if @conf['antirefspam.comment_ngwords'] != nil
+          ngwords = @conf['antirefspam.comment_ngwords']
+          ngwords.to_s.each_line do |ngword|
+            ngword.sub!(/\r?\n|\r/,'')
+            if comment.body.downcase.include? ngword.downcase
+              log_spamcomment( comment )
+              return false
+            end
+
+            # 含まれなかった場合は "NGワード" を正規表現とみなして再チェック
+            begin
+              if comment.body =~ Regexp.new( ngword, Regexp::MULTILINE )
+                log_spamcomment( comment )
+                return false
+              end
+            rescue
+              debug_out("error_config", "comment_ngwords: "+ngword)
+            end
+          end
+        end
+
+        return true
+      end
+    end
+  end
+end
Index: /tdiary/branches/upstream/contrib/filter/comment_key.rb
===================================================================
--- /tdiary/branches/upstream/contrib/filter/comment_key.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/filter/comment_key.rb (revision 710)
@@ -0,0 +1,29 @@
+#
+# comment_key.rb: Comment-key filter  Ver.0.5.0
+#  included TDiary::Filter::CommentKeyFilter class
+#
+# caution:
+#   * This filter must use together plugin 'comment_key.rb'.
+#
+# see:
+#   http://www20.big.or.jp/~rin_ne/soft/tdiary/commentkey.htm
+#
+# Copyright (c) 2005 Hahahaha <rin_ne@big.or.jp>
+# Distributed under the GPL
+#
+
+module TDiary
+	module Filter
+		class CommentKeyFilter < Filter
+			def comment_filter( diary, comment )
+				return true unless @conf['comment_key.enable']
+				return true if /^(?:TrackBack|Pingback)$/ =~ comment.name
+
+				require 'digest/md5'
+				keyprefix = @conf['comment_key.prefix'] || 'tdiary'
+				vkey = Digest::MD5.hexdigest(keyprefix + (@conf['comment_key.nodate'] == 'true' ? "" : @cgi.params['date'][0]))
+				vkey == @cgi.params['comment_key'][0]
+			end
+		end
+	end
+end
Index: /tdiary/branches/upstream/contrib/filter/spamlookup.rb
===================================================================
--- /tdiary/branches/upstream/contrib/filter/spamlookup.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/filter/spamlookup.rb (revision 710)
@@ -0,0 +1,37 @@
+#
+# spamlookup.rb: included TDiary::Filter::SpamlookupFilter class
+#
+
+require 'resolv'
+require 'uri'
+
+module TDiary
+  module Filter
+    class SpamlookupFilter < Filter
+      def black_domain?( domain )
+        begin
+          Resolv.getaddress( "#{domain}.rbl.bulkfeeds.jp" )
+          return true
+        rescue
+        end
+        false
+      end
+
+      def black_url?( body )
+        URI.extract( body, %w[http] ) do |url|
+          domain = URI.parse( url ).host.sub( /\.$/, '' )
+          return true if black_domain?( domain )
+        end
+        false
+      end
+
+      def comment_filter( diary, comment )
+        !black_url?( comment.body )
+      end
+
+      def referer_filter( referer )
+        !black_url?( referer )
+      end
+    end
+  end
+end
Index: /tdiary/branches/upstream/contrib/filter/comment_size.rb
===================================================================
--- /tdiary/branches/upstream/contrib/filter/comment_size.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/filter/comment_size.rb (revision 710)
@@ -0,0 +1,18 @@
+#
+# comment_size.rb: included TDiary::Filter::CommentSizeFilter class
+#
+
+module TDiary
+   module Filter
+      class CommentSizeFilter < Filter
+         def comment_filter( diary, comment )
+            return false if comment.body.size > @conf['comment.size']
+            true
+         end
+         
+         def referer_filter( referer )
+            true
+         end
+      end
+   end
+end
Index: /tdiary/branches/upstream/contrib/filter/hidecomment.rb
===================================================================
--- /tdiary/branches/upstream/contrib/filter/hidecomment.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/filter/hidecomment.rb (revision 710)
@@ -0,0 +1,13 @@
+#
+# ref. http://www.cozmixng.org/retro/projects/tdiary/ticket/60
+#
+
+module TDiary::Filter
+   class HidecommentFilter < Filter
+      def comment_filter( diary, comment )
+         comment.show = false # ツッコミを非表示にするが
+         true # spam扱いにはしない
+      end
+   end
+end
+
Index: /tdiary/branches/upstream/contrib/.autotest
===================================================================
--- /tdiary/branches/upstream/contrib/.autotest (revision 710)
+++ /tdiary/branches/upstream/contrib/.autotest (revision 710)
@@ -0,0 +1,5 @@
+Autotest.add_hook :initialize do |at|
+  at.add_mapping(%r!^plugin/(.*)\.rb$!) {|_, m|
+		at.files_matching %r!^spec/#{m[1]}_spec\.rb$!
+  }
+end
Index: /tdiary/branches/upstream/contrib/lib/bayes.rb
===================================================================
--- /tdiary/branches/upstream/contrib/lib/bayes.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/lib/bayes.rb (revision 710)
@@ -0,0 +1,235 @@
+# Copyright (C) 2007, KURODA Hiraku <hiraku@hinet.mydns.jp>
+# You can redistribute it and/or modify it under GPL2. 
+
+require "pstore"
+require "uri"
+
+module Bayes
+	module CHARSET
+		def self.setup_re(m)
+			o = $KCODE
+			$KCODE = m::KCODE
+			m.const_set(:RE_MESSAGE_TOKEN, Regexp.union(m::RE_KATAKANA, m::RE_KANJI, /[a-zA-Z]+/))
+			$KCODE=o
+		end
+
+		module EUC
+			KCODE = "e"
+			KATAKANA = "\xa5\xa2-\xa5\xf3"
+			BAR = "\xa1\xbc"
+			KANJI = "\xb0\xa1-\xfc\xfe"
+			RE_KATAKANA = /[#{KATAKANA}#{BAR}]{2,}/eo
+			RE_KANJI = /[#{KANJI}]{2,}/eo
+
+			CHARSET.setup_re(self)
+		end
+
+		module UTF8
+			KCODE = "u"
+			def self.c2u(c)
+				[c].pack("U")
+			end
+			def self.utf_range(a, b)
+				"#{c2u(a)}-#{c2u(b)}"
+			end
+			KATAKANA = utf_range(0x30a0, 0x30ff)
+			BAR = c2u(0x30fc)
+			KANJI = utf_range(0x4e00, 0x9faf)
+			RE_KATAKANA = /[#{KATAKANA}#{BAR}]{2,}/uo
+			RE_KANJI = /[#{KANJI}]{2,}/uo
+
+			CHARSET.setup_re(self)
+		end
+	end
+
+	class TokenList < Array
+		attr_reader :charset
+
+		def initialize(charset=nil)
+			unless charset
+				charset =
+					case $KCODE
+					when /^e/i
+						CHARSET::EUC
+					else
+						CHARSET::UTF8
+					end
+			end
+			@charset = charset
+		end
+
+		alias _concat concat
+		def concat(array, prefix=nil)
+			if prefix
+				_concat(array.map{|i| "#{prefix} #{i.to_s}"})
+			else
+				_concat(array)
+			end
+		end
+
+		alias _push push
+		def push(item, prefix=nil)
+			if prefix
+				_push("#{prefix} #{item.to_s}")
+			else
+				_push(item)
+			end
+		end
+
+		def add_host(host, prefix=nil)
+			if /^(?:\d{1,3}\.){3}\d{1,3}$/ =~ host
+				while host.size>0
+					push(host, prefix)
+					host = host[/^(.*?)\.?\d+$/, 1]
+				end
+			else
+				push(host, prefix)
+
+				h = host
+				while /^(.*?)[-_.](.*)$/=~h
+					h = $2
+					push($1, prefix)
+					push(h, prefix)
+				end
+			end
+			self
+		end
+
+		def add_url(url, prefix=nil)
+			if URI.regexp(%w[http https ftp]) === url
+				url  = URI.parse url
+				host = url.host                       # $4
+				path = url.path.gsub(%r{^/+|/+$}, '') # $7
+
+				add_host(host, prefix)
+
+				if path.size>0
+					push(path, prefix)
+
+					p = path
+					re = %r[^(.*)[-_./](.*?)$]
+					while re=~p
+						p = $1
+						push($2, prefix)
+						push(p, prefix)
+					end
+				end
+			end
+			self
+		end
+
+		def add_message(message, prefix=nil)
+			concat(message.scan(@charset::RE_MESSAGE_TOKEN), prefix)
+			self
+		end
+
+		def add_mail_addr(addr, prefix=nil)
+			push(addr, prefix)
+
+			name, host = addr.split(/@/)
+			return self if (name||"").empty?
+			host ||= ""
+			push(name, prefix)
+			add_host(host, prefix)
+			self
+		end
+	end
+
+	class FilterBase
+		attr_reader :spam, :ham, :db_name
+
+		def initialize(db_name=nil)
+			@spam = self.class::Corpus.new
+			@ham = self.class::Corpus.new
+
+			@db_name = db_name
+			if db_name && File.exist?(db_name)
+				PStore.new(db_name).transaction(true) do |db|
+					@spam = db["spam"]
+					@ham = db["ham"]
+				end
+			end
+		end
+
+		def save(db_name=nil)
+			db_name ||= @db_name
+			@db_name ||= db_name
+			return unless @db_name
+			PStore.new(@db_name).transaction do |db|
+				db["spam"] = @spam
+				db["ham"] = @ham
+				yield(db) if block_given?
+			end
+		end
+
+		def [](token)
+			score(token)
+		end
+	end
+
+	class PlainBayes < FilterBase
+		class Corpus < Hash
+			def initialize
+				super(0.0)
+			end
+
+			def <<(src)
+				s = src.size.to_f
+				src.each do |i|
+					self[i] += 1/s
+				end
+			end
+		end
+
+		def score(token)
+			return nil unless @spam.include?(token) || @ham.include?(token)
+			s = @spam[token]
+			h = @ham[token]
+			s/(s+h)
+		end
+
+		def estimate(tokens, take=15)
+			s = tokens.uniq.map{|i| score(i)}.compact.sort{|a, b| (0.5-a).abs <=> (0.5-b)}.reverse[0...take]
+			return nil if s.empty? || s.include?(1.0) && s.include?(0.0)
+
+			prod = s.inject(1.0){|r, i| r*i}
+			return prod/(prod+s.inject(1.0){|r, i| r*(1-i)})
+		end
+	end
+
+	class PaulGraham < FilterBase
+		class Corpus < Hash
+			attr_reader :count
+			def initialize
+				super(0)
+				@count = 0
+			end
+
+			def <<(src)
+				@count += 1
+				src.each do |i|
+					self[i] += 1
+				end
+			end
+		end
+
+		def score(token)
+			return 0.4 unless @spam.include?(token) or @ham.include?(token)
+			g = @ham.count==0 ? 0.0 : [1.0, 2*@ham[token]/@ham.count.to_f].min
+			b = @spam.count==0 ? 0.0 : [1.0, @spam[token]/@spam.count.to_f].min
+			if g+b==0
+				raise "OOO"
+			end
+			r = [0.01, [0.99, b/(g+b)].min].max
+			r
+		end
+
+		def estimate(tokens, take=15)
+			s = tokens.uniq.map{|i| score(i)}.compact.sort{|a, b| (0.5-a).abs <=> (0.5-b)}.reverse[0...take]
+			return nil if s.empty? || s.include?(1.0) && s.include?(0.0)
+
+			prod = s.inject(1.0){|r, i| r*i}
+			return prod/(prod+s.inject(1.0){|r, i| r*(1-i)})
+		end
+	end
+end
Index: /tdiary/branches/upstream/contrib/lib/exifparser/BUGS
===================================================================
--- /tdiary/branches/upstream/contrib/lib/exifparser/BUGS (revision 710)
+++ /tdiary/branches/upstream/contrib/lib/exifparser/BUGS (revision 710)
@@ -0,0 +1,1 @@
+* DO NOT USE WITH ruby-libexif!
Index: /tdiary/branches/upstream/contrib/lib/exifparser/sample/exifview.rb
===================================================================
--- /tdiary/branches/upstream/contrib/lib/exifparser/sample/exifview.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/lib/exifparser/sample/exifview.rb (revision 710)
@@ -0,0 +1,279 @@
+#!/usr/bin/env ruby
+#
+#   exifview.rb - display EXIF information as well as EXIF image
+#
+#   Copyright (C) 2002 Ryuichi Tamura (r-tam@fsinet.or.jp)
+#
+#   $Revision: 1.3 $
+#   $Date: 2002/12/17 05:16:34 $
+#
+#   Requirements: gtk2
+#   
+require 'exifparser'
+require 'observer'
+require 'gtk2'
+
+module Exif
+
+  class Parser
+
+    def each_values
+      tags.each do |tag|
+        next if tag == Tag::Exif::MakerNote
+        yield tag.name, tag.tagID.to_s, tag.format, tag.IFD, tag.to_s
+      end
+    end
+
+  end
+
+end
+
+module ExifView
+
+  class SelectedFile
+    include Observable
+
+    def initialize
+      @fpath = nil
+    end
+    attr_reader :fpath
+    
+    def filename=(filename)
+      @fpath = File.expand_path(filename)
+      begin
+        exif = ExifParser.new(@fpath)
+        changed
+      rescue
+        changed(false)
+        raise
+      end
+      notify_observers(@fpath, exif)
+    end
+
+  end
+
+  DisplayImage = SelectedFile.new
+
+  #
+  # Error dialog
+  #
+  class ErrorDialog < ::Gtk::Dialog
+
+    def initialize(msg)
+      super()
+      self.set_default_size(200, 100)
+      self.set_title("ExifView: Error")
+      button = Gtk::Button.new("dismiss")
+      button.flags |= Gtk::Widget::CAN_DEFAULT
+      button.signal_connect("clicked") do destroy end
+      self.action_area.pack_start(button, 0)
+      button.grab_default
+      label = Gtk::Label.new(msg)
+      vbox.pack_start(label)
+      button.show
+      label.show
+    end
+
+  end
+
+  #
+  # File selection
+  #
+  class FileSelectionWidget < ::Gtk::FileSelection
+
+    def initialize
+      super('File selection')
+      history_pulldown
+      self.signal_connect('destroy') { destroy }
+      self.ok_button.signal_connect('clicked') {
+        catch(:read_error) {
+          begin
+            DisplayImage.filename = self.filename
+          rescue
+            ErrorDialog.new($!.message).show
+            throw :read_error
+          end
+          destroy
+        }
+      }
+      self.cancel_button.signal_connect('clicked') { destroy }
+    end
+
+  end
+
+  #
+  # MenuItem: Open
+  #
+  class OpenMenuItemWidget < ::Gtk::MenuItem
+
+    def initialize
+      super('Open')
+      @filesel = nil
+      self.signal_connect('activate') { 
+        @filesel = FileSelectionWidget.new
+        @filesel.show 
+      }
+    end
+    attr_reader :filesel
+
+  end
+
+  #
+  # MenuItem: Quit
+  #
+  class QuitMenuItemWidget < ::Gtk::MenuItem
+
+    def initialize
+      super('Quit')
+      self.signal_connect('activate') { Gtk.main_quit }
+    end
+
+  end
+  
+  #
+  # Menu Bar 
+  #
+  class MenuBarWidget < ::Gtk::MenuBar
+
+    def initialize
+      super()
+      @item_open =  OpenMenuItemWidget.new
+      @item_quit =  QuitMenuItemWidget.new
+      menu = Gtk::Menu.new
+      menu.append(@item_open)
+      menu.append(@item_quit)
+      filemenu = Gtk::MenuItem.new("File")
+      filemenu.set_submenu(menu)
+      self.append(filemenu)
+    end
+
+  end
+
+  class ImageWindow < ::Gtk::ScrolledWindow
+
+    def initialize
+      super(nil, nil)
+      DisplayImage.add_observer(self)
+      @filename = nil
+      @image = Gtk::Image.new
+      @vbox = Gtk::VBox.new(false, 0)
+      self.add_with_viewport(@vbox)
+      self.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC)
+      @vbox.pack_start(@image, false, false, 0)
+    end
+    attr_reader :filename
+    
+    def update(*args)
+      @filename, = *args
+      @image.set(filename)
+    end
+
+  end
+
+  class ExifDataWindow < ::Gtk::ScrolledWindow
+
+    def initialize
+      super(nil, nil)
+      DisplayImage.add_observer(self)
+      @model, @treeview = setup_columns()
+    end
+
+    def setup_columns
+      model = Gtk::ListStore.new(String, String, String, String, String)
+      # tagname, tag_id, ifd_name, value, respectedly
+      treeview = Gtk::TreeView.new(model)
+      cols = []; i = 0
+      [["Name", 0], ["Number", 1], ["Format", 2], 
+        ["IFD", 3], ["Value", 4]].each do |e|
+        treeview.insert_column(
+          Gtk::TreeViewColumn.new(e[0], Gtk::CellRendererText.new, 
+                                  {:text => e[1]}), e[1] )
+      end
+      treeview.selection.set_mode(Gtk::SELECTION_SINGLE)
+      self.add_with_viewport(treeview)
+      [model, treeview]
+    end
+    
+    def update(*args)
+      fpath, exif = *args
+      @model.clear
+      exif.each_values do |e| 
+        set_row(*e)
+      end
+    end
+
+    private
+
+    def set_row(tagname, tag_id, tag_format, ifd_name, value)
+      iter = @model.append
+      iter.set_value(0, tagname)
+      iter.set_value(1, tag_id)
+      iter.set_value(2, tag_format)
+      iter.set_value(3, ifd_name)
+      iter.set_value(4, value)
+    end
+
+  end
+
+  class MainWindow < ::Gtk::Window
+
+    def initialize(*args)
+      super(*args)
+      DisplayImage.add_observer(self)
+      # Components lower: Exif data
+      @exifdata = ExifDataWindow.new
+
+      # Components middle: Image
+      @image = ImageWindow.new
+
+      # Components upper: Menu bar
+      @menubar = MenuBarWidget.new
+
+      # VBox creation
+      @vbox = Gtk::VBox.new(false, 0)
+      
+      # signal connection for toplevel window
+      self.signal_connect('destroy') { exit }
+      self.signal_connect('delete_event') { exit }
+    end
+
+    def initialize_display 
+      set_appearance()
+      self.show_all
+    end
+
+    def update(*args)
+      filename, = *args
+      self.set_title("ExifView: #{filename}")
+    end
+
+    private
+
+    def set_appearance
+      # self
+      self.set_size_request(640,480)
+      self.set_title("ExifView #{@image.filename}")
+      self.add(@vbox)
+      # menubar
+      @vbox.pack_start(@menubar, false, false, 0)
+      # image display
+      @vbox.pack_start(@image, true, true, 0)
+      # exif data display
+      @vbox.pack_start(@exifdata, true, true, 0)
+    end
+
+  end
+
+end
+
+if $0 == __FILE__
+  Gtk.init
+  #
+  # Main
+  #
+  filename = ARGV.shift
+  window = ExifView::MainWindow.new(Gtk::Window::TOPLEVEL)
+  ExifView::DisplayImage.filename = filename if filename
+  window.initialize_display
+  Gtk.main
+end
Index: /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/utils.rb
===================================================================
--- /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/utils.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/utils.rb (revision 710)
@@ -0,0 +1,80 @@
+#
+#   exifparser/utils.rb -
+#
+#   Copyright (C) 2002 Ryuichi Tamura (r-tam@fsinet.or.jp)
+#
+#    $Revision: 1.1.1.1 $
+#    $Date: 2002/12/16 07:59:00 $
+#
+
+module Exif
+
+  #
+  # utility module that will be included in some classes.
+  #
+  module Utils
+
+    module Decode
+
+      module Motorola
+
+        def byte_order
+          :motorola
+        end
+
+        def decode_ushort(str)
+          str[0,2].unpack('n').first
+        end
+
+        def decode_ulong(str)
+          str[0,4].unpack('N').first
+        end
+
+        def decode_sshort(str)
+          str[0,2].unpack('n').pack('s').unpack('s').first
+        end
+
+        def decode_slong(str)
+          str[0,4].unpack('N').pack('l').unpack('l').first
+        end
+
+        def parseTagID(str)
+          sprintf("0x%02x%02x", *(str.unpack("C*")))
+        end
+
+
+      end
+
+      module Intel
+
+        def byte_order
+          :intel
+        end
+
+        def decode_ushort(str)
+          str[0,2].unpack('v').first
+        end
+
+        def decode_ulong(str)
+          str[0,4].unpack('V').first
+        end
+
+        def decode_sshort(str)
+          str[0,2].unpack('s').first
+        end
+
+        def decode_slong(str)
+          str[0,4].unpack('l').first
+        end
+
+        def parseTagID(str)
+          "0x" + str.unpack("C*").reverse.collect{ |e| sprintf("%02x", e) }.join("")
+        end
+
+      end
+
+    end
+
+  end
+
+end
Index: /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/makernote/canon.rb
===================================================================
--- /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/makernote/canon.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/makernote/canon.rb (revision 710)
@@ -0,0 +1,502 @@
+#
+#   exifparser/makernote/nikon.rb -
+#
+#   Copyright (C) 2002 Ryuichi Tamura (r-tam@fsinet.or.jp)
+#
+#   $Revision: 1.1.1.1 $
+#   $Date: 2002/12/16 07:59:00 $
+#
+require 'exifparser/tag'
+require 'exifparser/utils'
+
+module Exif
+
+  module Tag
+
+    module MakerNote
+
+      #
+      # 0x0000 - Unknown
+      #
+
+      #
+      # 0x0001 - Tag0x0001
+      #
+      class Tag0x0001 < Base
+
+        def processData
+          @formatted = []
+          partition_data(@count) do |part|
+            @formatted.push _formatData(part)
+          end
+        end
+
+        def value
+          numTags = @formatted[0] / 2
+          ret = {}
+
+          return ret if numTags < 2
+          #
+          # offset 1 : Macro mode
+          #
+          ret["Macro mode"] =
+          case @formatted[1]
+          when 1
+            "Macro"
+          when 2
+            "Normal"
+          else
+            "Unknown"
+          end
+
+          return ret if numTags < 3
+          #
+          # offset 2 : if nonzero, length of self-timer in 10ths of a second.
+          #
+          selftimer_length = @formatted[2]
+
+          return ret if numTags < 5
+          #
+          # offset 4 : Flash mode
+          #
+          ret["Flash mode"] =
+          case @formatted[4]
+          when 0
+            "flash not fired"
+          when 1
+            "auto"
+          when 2
+            "on"
+          when 3
+            "red-eye reduction"
+          when 4
+            "slow synchro"
+          when 5
+            "auto + redeye reduction"
+          when 6
+            "on + redeye reduction"
+          when 16
+            "external flash"
+          else
+            "unknown"
+          end
+
+          return ret if numTags < 6
+          #
+          # offset 5: Contiuous drive mode
+          #
+          ret["Continuous drive mode"] =
+          case @formatted[5]
+          when 0
+            if selftimer_length != 0
+              "Timer = #{selftimer_length/10.0}sec."
+            else
+              "Single"
+            end
+          when 1
+            "Continuous"
+          end
+
+          return ret if numTags < 8
+          #
+          # offset 7: Focus Mode
+          #
+          ret["Focus Mode"] =
+          case @formatted[7]
+          when 0
+            "One-Shot"
+          when 1
+            "AI Servo"
+          when 2
+            "AI Focus"
+          when 3
+            "MF"
+          when 4
+            "Single"
+          when 5
+            "Continuous"
+          when 6
+            "MF"
+          else
+            "Unknown"
+          end
+
+          return ret if numTags < 11
+          #
+          # offset 10: Image size
+          #
+          ret["Image Size"] =
+          case @formatted[10]
+          when 0
+            "Large"
+          when 1
+            "Medium"
+          when
+            "Small"
+          else
+            "Unknown"
+          end
+
+          return ret if numTags < 12
+          #
+          # offset 11: "Easy shooting" mode
+          #
+          ret["Easy shooting mode"] =
+          case @formatted[11]
+          when 0
+            "Full auto"
+          when 1
+            "Manual"
+          when 2
+            "Landscape"
+          when 3
+            "Fast Shutter"
+          when 4
+            "Slow Shutter"
+          when 5
+            "Night"
+          when 6
+            "B&W"
+          when 7
+            "Sepia"
+          when 8
+            "Portrait"
+          when 9
+            "Sports"
+          when 10
+            "Macro / Close-Up"
+          when 11
+            "Pan Focus"
+          else
+            "Unknown"
+          end
+
+          return ret if numTags < 14
+          #
+          # offset 13: Contrast
+          #
+          ret["Contrast"] =
+          case @formatted[13]
+          when 0xffff
+            "Low"
+          when 0x0000
+            "Normal"
+          when 0x0001
+            "High"
+          else
+            "Unknown"
+          end
+
+          return ret if numTags < 15
+          #
+          # offset 14: Saturation
+          #
+          ret["Saturation"] =
+          case @formatted[14]
+          when 0xffff
+            "Low"
+          when 0x0000
+            "Normal"
+          when 0x0001
+            "High"
+          else
+            "Unknown"
+          end
+
+          return ret if numTags < 16
+          #
+          # offset 15: Contrast
+          #
+          ret["Sharpness"] =
+          case @formatted[15]
+          when 0xffff
+            "Low"
+          when 0x0000
+            "Normal"
+          when 0x0001
+            "High"
+          else
+            "Unknown"
+          end
+
+          return ret if numTags < 17
+          #
+          # offset 16: ISO
+          #
+          ret["ISO"] =
+          case @formatted[16]
+          when 0
+            "ISOSpeedRatings"
+          when 15
+            "Auto"
+          when 16
+            50
+          when 17
+            100
+          when 18
+            200
+          when 19
+            400
+          else
+            "Unknown"
+          end
+
+          return ret if numTags < 18
+          #
+          # offset 17: Metering mode
+          #
+          ret['Metering mode'] =
+          case @formatted[17]
+          when 3
+            "Evaluative"
+          when 4
+            "Partial"
+          when 5
+            "Center-weighted"
+          else
+            "Unknown"
+          end
+          ret
+        end
+
+      end
+
+      #
+      # 0x0003 - Tag0x0003
+      #
+      class Tag0x0003 < Base
+      end
+
+      #
+      # 0x0004 - Tag0x0004
+      #
+      class Tag0x0004 < Base
+
+        def processData
+          @formatted = []
+          partition_data(@count) do |part|
+            @formatted.push _formatData(part)
+          end
+        end
+
+        def value
+          numTags = @formatted[0] / 2
+          ret = {}
+
+          return hash if numTags < 8
+          # offset 7 : white balance
+          ret['White balance'] =
+          case @formatted[7]
+          when 0
+            "Auto"
+          when 1
+            "Sunny"
+          when 2
+            "Cloudy"
+          when 3
+            "Tungsten"
+          when 4
+            "Florescent"
+          when 5
+            "Flash"
+          when 6
+            "Custom"
+          else
+            "Unknown"
+          end
+
+          return ret if numTags < 10
+          # offset 9: Sequence number (if in a continuous burst)
+          ret['Sequence number'] = @formatted[9]
+
+          return ret if numTags < 15
+          # offset 14: Auto Focus point used
+          ret['Auto Focus point used'] = @formatted[14]
+
+          return ret if numTags < 16
+          ret['Flash bias'] =
+          case @formatted[15]
+          when 0xffc0
+            "-2 EV"
+          when 0xffcc
+            "-1.67 EV"
+          when 0xffd0
+            "-1.50 EV"
+          when 0xffd4
+            "-1.33 EV"
+          when 0xffe0
+            "-1 EV"
+          when 0xffec
+            "-0.67 EV"
+          when 0xfff0
+            "-0.50 EV"
+          when 0xfff4
+            "-0.33 EV"
+          when 0x0000
+            "0 EV"
+          when 0x000c
+            "0.33 EV"
+          when 0x0010
+            "0.50 EV"
+          when 0x0014
+            "0.67 EV"
+          when 0x0020
+            "1 EV"
+          when 0x002c
+            "1.33 EV"
+          when 0x0030
+            "1.50 EV"
+          when 0x0034
+            "1.67 EV"
+          when 0x0040
+            "2 EV"
+          else
+            "Unknown"
+          end
+
+          return ret if numTags < 20
+          ret['Subject Distance'] = @formatted[19]
+
+          ret
+        end
+
+      end
+
+      #
+      # 0x0006 - ImageType
+      #
+      class ImageType < Base
+      end
+
+      #
+      # 0x0007 - FirmwareVersion
+      #
+      class FirmwareVersion < Base
+      end
+
+      #
+      # 0x0008 - ImageNumber
+      #
+      class ImageNumber < Base
+      end
+
+      #
+      # 0x0009 - OwnerName
+      #
+      class OwnerName < Base
+      end
+
+      #
+      # 0x000a - Unknown
+      #
+
+      #
+      # 0x000c - CameraSerialNumber
+      #
+      class CameraSerialNumber < Base
+
+        def to_s
+          hi = @formatted / 0x10000
+          low = @formatted % 0x10000
+          "%04X%05d"%[hi, low]
+        end
+
+      end
+
+      #
+      # 0x000d - Unknown
+      #
+
+      #
+      # 0x000f - CustomFunctions
+      #
+      class CustomFunctions < Base
+
+        def processData
+          @formatted = []
+          partition_data(@count) do |part|
+            @formatted.push _formatData(part)
+          end
+        end
+
+      end
+
+    end
+
+    CanonIFDTable = {
+      0x0000 => Unknown,
+      0x0001 => MakerNote::Tag0x0001,
+      0x0003 => MakerNote::Tag0x0003,
+      0x0004 => MakerNote::Tag0x0004,
+      0x0006 => MakerNote::ImageType,
+      0x0007 => MakerNote::FirmwareVersion,
+      0x0008 => MakerNote::ImageNumber,
+      0x0009 => MakerNote::OwnerName,
+      0x000a => Unknown,
+      0x000c => MakerNote::CameraSerialNumber,
+      0x000d => Unknown,
+      0x000f => MakerNote::CustomFunctions
+    }
+
+  end
+
+  class Canon
+
+    def initialize(fin, tiff_origin, dataPos, byteOrder_module)
+      @fin = fin
+      @tiffHeader0 = tiff_origin
+      @dataPos = dataPos
+      @byteOrder_module = byteOrder_module
+      self.extend @byteOrder_module
+    end
+
+    def scan_IFD
+      #
+      # Canon MakerNote starts from 0
+      #
+      @fin.pos = @dataPos + 0
+      #
+      # get the number of tags
+      #
+      numDirs = decode_ushort(fin_read_n(2))
+      #
+      # now scan them
+      #
+      1.upto(numDirs) {
+        curpos_tag = @fin.pos
+        tag = parseTagID(fin_read_n(2))
+        tagclass = Tag.find(tag.hex, Tag::CanonIFDTable)
+        unit, formatter = Tag::Format::Unit[decode_ushort(fin_read_n(2))]
+        count = decode_ulong(fin_read_n(4))
+        tagdata = fin_read_n(4)
+        obj = tagclass.new(tag, "MakerNote", count)
+        obj.extend formatter, @byteOrder_module
+        obj.pos = curpos_tag
+        if unit * count > 4
+          curpos = @fin.pos
+          begin
+            @fin.pos = @tiffHeader0 + decode_ulong(tagdata)
+            obj.dataPos = @fin.pos
+            obj.data = fin_read_n(unit*count)
+          ensure
+            @fin.pos = curpos
+          end
+        else
+          obj.dataPos = @fin.pos - 4
+          obj.data = tagdata
+        end
+        obj.processData
+        yield obj
+      }
+    end
+
+    private
+
+    def fin_read_n(n)
+      @fin.read(n)
+    end
+
+  end
+
+
+end
Index: /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/makernote/nikon.rb
===================================================================
--- /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/makernote/nikon.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/makernote/nikon.rb (revision 710)
@@ -0,0 +1,267 @@
+#
+#   exifparser/makernote/nikon.rb -
+#
+#   Copyright (C) 2002 Ryuichi Tamura (r-tam@fsinet.or.jp)
+#
+#   $Revision: 1.1.1.1 $
+#   $Date: 2002/12/16 07:59:00 $
+#
+require 'exifparser/tag'
+require 'exifparser/utils'
+
+module Exif
+
+  module Tag
+
+    module MakerNote
+
+      #
+      # 0x0003 - Quality
+      #
+      class Quality < Base
+
+        def to_s
+          n = @formatted.to_i - 1
+          (s, q) = n.divmod(3)
+
+          f =
+            case s
+            when 0
+              'VGA'
+            when 1
+              'SVGA'
+            when 2
+              'SXGA'
+            when 3
+              'UXGA'
+            else
+              'Unknown size'
+          end
+
+          f << ' ' <<
+            case q
+            when 0
+              'Basic'
+            when 1
+              'Normal'
+            when 2
+              'Fine'
+            else
+              'Unknown quality'
+            end
+          f
+        end
+
+      end
+
+      #
+      # 0x0004 - ColorMode
+      #
+      class ColorMode < Base
+
+        def to_s
+          case @formatted
+          when 1
+            'Color'
+          when 2
+            'Monochrome'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x0005 - ImageAdjustment
+      #
+      class ImageAdjustment < Base
+
+        def to_s
+          case @formatted
+          when 0
+            'Normal'
+          when 1
+            'Bright+'
+          when 2
+            'Bright-'
+          when 3
+            'Contrast+'
+          when 4
+            'Contrast-'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x0006 - CCDSensitivity
+      #
+      class CCDSensitivity < Base
+
+        def to_s
+          case @formatted
+          when 0
+            'ISO80'
+          when 2
+            'ISO160'
+          when 4
+            'ISO320'
+          when 5
+            'ISO100'
+          else
+            "Unknown(#{@formatted})"
+          end
+      end
+
+      end
+
+      #
+      # 0x0007 - WhiteBalance
+      #
+      class WhiteBalance < Base
+
+        def to_s
+          case @formatted
+          when 0
+            'Auto'
+          when 1
+            'Preset'
+          when 2
+            'Daylight'
+          when 3
+            'Incandescense'
+          when 4
+            'Fluorescence'
+          when 5
+            'Cloudy'
+          when 6
+            'SpeedLight'
+          else
+            "Unknown(#{@formatted})"
+          end
+      end
+
+      end
+
+      #
+      # 0x0008 - Focus
+      #
+      class Focus < Base
+
+        def to_s
+          n = @formatted.numerator
+          d = @formatted.denominator
+          (n == 1 && d == 0) ? 'Pan Focus' : "#{n}/#{d}"
+        end
+
+      end
+
+      #
+      # 0x000a - DigitalZoom
+      #
+      class DigitalZoom < Base
+
+        def to_s
+          n = @formatted.numerator
+          d = @formatted.denominator
+          (n == 0 && d == 100) ? 'None' : "%0.1f"%[n.to_f/d.to_f]
+        end
+
+      end
+
+      #
+      # 0x000b - Converter
+      #
+      class Converter < Base
+
+        def to_s
+          case @formatted
+          when 0
+            'None'
+          when 1
+            'Fisheye'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+    end
+
+    NikonIFDTable = {
+      0x0003 => MakerNote::Quality,
+      0x0004 => MakerNote::ColorMode,
+      0x0005 => MakerNote::ImageAdjustment,
+      0x0006 => MakerNote::CCDSensitivity,
+      0x0007 => MakerNote::WhiteBalance,
+      0x0008 => MakerNote::Focus,
+      0x000a => MakerNote::DigitalZoom,
+      0x000b => MakerNote::Converter
+    }
+
+  end
+
+  class Nikon
+
+    def initialize(fin, tiff_origin, dataPos, byteOrder_module)
+      @fin = fin
+      @tiffHeader0 = tiff_origin
+      @dataPos = dataPos
+      @byteOrder_module = byteOrder_module
+      self.extend @byteOrder_module
+    end
+
+    def scan_IFD
+      #
+      # Nikon MakerNote starts from 8 byte from the origin.
+      #
+      @fin.pos = @dataPos + 8
+      #
+      # get the number of tags
+      #
+      num_dirs = decode_ushort(fin_read_n(2))
+
+      #
+      # now scan them
+      #
+      1.upto(num_dirs) {
+        curpos_tag = @fin.pos
+        tag = parseTagID(fin_read_n(2))
+        tagclass = Tag.find(tag.hex, Tag::NikonIFDTable)
+        unit, formatter = Tag::Format::Unit[decode_ushort(fin_read_n(2))]
+        count = decode_ulong(fin_read_n(4))
+        tagdata = fin_read_n(4)
+        obj = tagclass.new(tag, "MakerNote", count)
+        obj.extend formatter, @byteOrder_module
+        obj.pos = curpos_tag
+        if unit * count > 4
+          curpos = @fin.pos
+          begin
+            @fin.pos = @tiffHeader0 + decode_ulong(tagdata)
+            obj.dataPos = @fin.pos
+            obj.data = fin_read_n(unit*count)
+          ensure
+            @fin.pos = curpos
+          end
+        else
+          obj.dataPos = @fin.pos - 4
+          obj.data = tagdata
+        end
+        obj.processData
+        yield obj
+      }
+    end
+
+    private
+
+    def fin_read_n(n)
+      @fin.read(n)
+    end
+
+  end
+
+end
Index: /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/makernote/nikon2.rb
===================================================================
--- /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/makernote/nikon2.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/makernote/nikon2.rb (revision 710)
@@ -0,0 +1,207 @@
+#
+#   exif/makernote/nikon2.rb
+#
+#   $Revision: 1.3 $
+#    $Date: 2003/04/27 13:54:52 $
+#
+#== Reference
+#
+#http://www.ba.wakwak.com/%7Etsuruzoh/Computer/Digicams/exif-e.html
+#
+require 'exifparser/tag'
+require 'exifparser/utils'
+
+module Exif
+
+  #
+  # Tags used in Nikon Makernote
+  #
+  module Tag
+
+    module MakerNote
+
+      #
+      # 0x0002 - ISOSetting
+      #
+      class ISOSetting < Base
+      end
+
+      #
+      # 0x0003 - ColorMode
+      #
+      class ColorMode < Base
+      end
+
+      #
+      # 0x0004 - Quality
+      #
+      class Quality < Base
+      end
+
+      #
+      # 0x0005 - Whitebalance
+      #
+      class Whitebalance < Base
+      end
+
+      #
+      # 0x0006 - ImageSharpening
+      #
+      class ImageSharpening < Base
+      end
+
+      #
+      # 0x0007 - FocusMode
+      #
+      class FocusMode < Base
+      end
+
+      #
+      # 0x0008 - FlashSetting
+      #
+      class FlashSetting < Base
+      end
+
+      #
+      # 0x000f - ISOSelection
+      #
+      class ISOSelection < Base
+      end
+
+      #
+      # 0x0010 - DataDump
+      #
+      class DataDump < Base
+      end
+
+      #
+      # 0x0080 - ImageAdjustment
+      #
+      class ImageAdjustment < Base
+      end
+
+      #
+      # 0x0082 - Adapter
+      #
+      class Adapter < Base
+      end
+
+      #
+      # 0x0085 - ManualForcusDistance
+      #
+      class ManualForcusDistance < Base
+      end
+
+      #
+      # 0x0086 - DigitalZoom
+      #
+      class DigitalZoom < Base
+      end
+
+      #
+      # 0x0088 - AFFocusPosition
+      #
+      class AFFocusPosition < Base
+      end
+
+    end
+
+    Nikon2IFDTable = {
+      0x0002 => MakerNote::ISOSetting,
+      0x0003 => MakerNote::ColorMode,
+      0x0004 => MakerNote::Quality,
+      0x0005 => MakerNote::Whitebalance,
+      0x0006 => MakerNote::ImageSharpening,
+      0x0007 => MakerNote::FocusMode,
+      0x0008 => MakerNote::FlashSetting,
+      0x000f => MakerNote::ISOSelection,
+      0x0010 => MakerNote::DataDump,
+      0x0080 => MakerNote::ImageAdjustment,
+      0x0082 => MakerNote::Adapter,
+      0x0085 => MakerNote::ManualForcusDistance,
+      0x0086 => MakerNote::DigitalZoom,
+      0x0088 => MakerNote::AFFocusPosition,
+    }
+
+  end
+
+  class Nikon2
+
+    def initialize(fin, tiff_origin, dataPos, byteOrder_module)
+      @fin = fin
+      @tiffHeader0 = tiff_origin
+      @dataPos = dataPos
+      @nikonOffset = 0
+
+      @fin.pos = dataPos
+      magic = fin_read_n(6)
+
+      if magic == "Nikon\000"
+        @nikonOffset = 18   # D100, E5700, etc..
+        fin_read_n(4)
+        @tiffHeader0 = @fin.pos
+        bo = @fin.read(2)
+        case bo
+        when "MM"
+          byteOrder_module = Utils::Decode::Motorola
+        when "II"
+          byteOrder_module = Utils::Decode::Intel
+        else
+          raise RuntimeError, "Unknown byte order"
+        end
+      end
+      @byteOrder_module = byteOrder_module
+      self.extend @byteOrder_module
+    end
+
+    def scan_IFD
+      #
+      # Nikon D1 series MakerNote starts from 0 byte from the origin.
+      #
+      @fin.pos = @dataPos + @nikonOffset
+
+      #
+      # get the number of tags
+      #
+      num_dirs = decode_ushort(fin_read_n(2))
+
+      #
+      # now scan them
+      #
+      1.upto(num_dirs) {
+        curpos_tag = @fin.pos
+        tag = parseTagID(fin_read_n(2))
+        tagclass = Tag.find(tag.hex, Tag::Nikon2IFDTable)
+        unit, formatter = Tag::Format::Unit[decode_ushort(fin_read_n(2))]
+        count = decode_ulong(fin_read_n(4))
+        tagdata = fin_read_n(4)
+        obj = tagclass.new(tag, "MakerNote", count)
+        obj.extend formatter, @byteOrder_module
+        obj.pos = curpos_tag
+        if unit * count > 4
+          curpos = @fin.pos
+          begin
+            @fin.pos = @tiffHeader0 + decode_ulong(tagdata)
+            obj.dataPos = @fin.pos
+            obj.data = fin_read_n(unit*count)
+          ensure
+            @fin.pos = curpos
+          end
+        else
+          obj.dataPos = @fin.pos - 4
+          obj.data = tagdata
+        end
+        obj.processData
+        yield obj
+      }
+    end
+
+    private
+
+    def fin_read_n(n)
+      @fin.read(n)
+    end
+
+  end
+
+end
Index: /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/makernote/minolta.rb
===================================================================
--- /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/makernote/minolta.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/makernote/minolta.rb (revision 710)
@@ -0,0 +1,84 @@
+#
+#   exifparser/makernote/minolta.rb
+#
+#   $Revision: 1.2 $
+#   $Date: 2003/05/13 15:41:28 $
+#
+require 'exifparser/tag'
+require 'exifparser/utils'
+
+module Exif
+
+  module Tag
+
+    module MakerNote
+    end
+
+    MinoltaIFDTable = {
+    }
+
+  end
+
+  class Minolta
+
+    def initialize(fin, tiff_origin, dataPos, byteOrder_module)
+      @fin = fin
+      @tiffHeader0 = tiff_origin
+      @dataPos = dataPos
+      @byteOrder_module = byteOrder_module
+      self.extend @byteOrder_module
+    end
+
+    def scan_IFD
+      #
+      # Minolta MakerNote starts from 0 byte from the origin
+      #
+
+      @fin.pos = @dataPos + 0
+
+      #
+      # get the number of tags
+      #
+      numDirs = decode_ushort(fin_read_n(2))
+
+      #
+      # now scan them
+      #
+      1.upto(numDirs) {
+        curpos_tag = @fin.pos
+        tag = parseTagID(fin_read_n(2))
+        tagclass = Tag.find(tag.hex, Tag::MinoltaIFDTable)
+        unit, formatter = Tag::Format::Unit[decode_ushort(fin_read_n(2))]
+        count = decode_ulong(fin_read_n(4))
+        tagdata = fin_read_n(4)
+
+        obj = tagclass.new(tag, "MakerNote", count)
+        obj.extend formatter, @byteOrder_module
+        obj.pos = curpos_tag
+        if unit * count > 4
+          curpos = @fin.pos
+          begin
+            @fin.pos = @tiffHeader0 + decode_ulong(tagdata)
+            obj.dataPos = @fin.pos
+            obj.data = fin_read_n(unit*count)
+          ensure
+            @fin.pos = curpos
+          end
+        else
+          obj.dataPos = @fin.pos - 4
+          obj.data = tagdata
+        end
+        obj.processData
+        yield obj
+      }
+    end
+
+    private
+
+    def fin_read_n(n)
+      @fin.read(n)
+    end
+
+  end
+
+end
Index: /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/makernote/fujifilm.rb
===================================================================
--- /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/makernote/fujifilm.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/makernote/fujifilm.rb (revision 710)
@@ -0,0 +1,415 @@
+#
+#   exifparser/makernote/fujifilm.rb -
+#
+#   Copyright (C) 2002 Ryuichi Tamura (r-tam@fsinet.or.jp)
+#
+#    $Revision: 1.1.1.1 $
+#    $Date: 2002/12/16 07:59:00 $
+#
+#    Tested against FinePix 6900Z
+#
+require 'exifparser/tag'
+require 'exifparser/utils'
+
+#
+#== References
+#http://www.ba.wakwak.com/%7Etsuruzoh/Computer/Digicams/exif-e.html
+#
+
+
+module Exif
+
+  module Tag
+
+    module MakerNote
+
+      #
+      # 0x0000 - Version
+      #
+      class Version < Base
+      end
+
+      #
+      # 0x1000 - Quality
+      #
+      class Quality < Base
+      end
+
+      #
+      # 0x1001 - Sharpness
+      #
+      class Sharpness < Base
+
+        def to_s
+          case @formatted
+          when 1,2
+            'Weak'
+          when 3
+            'Standard'
+          when 4
+            'Strong'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x1002 - White
+      #
+      class White < Base
+
+        def to_s
+          case @formatted
+          when 0
+            'Auto'
+          when 256
+            'Daylight'
+          when 512
+            'Cloudy'
+          when 768
+            'DaylightColor-fluorescence'
+          when 769
+            'DaywhiteColor-fluorescence'
+          when 770
+            'WhiteColor-fluorescence'
+          when 1024
+            'Incandescence'
+          when 3840
+            'Custom white balance'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x1003 - Color
+      #
+      class Color < Base
+
+        def to_s
+          case @formatted
+          when 0
+            'Normal(STD)'
+          when 256
+            'High(HARD)'
+          when
+            'Low(ORG)'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x1004 - Tone
+      #
+      class Tone < Base
+
+        def to_s
+          case @formatted
+          when 0
+            'Normal(STD)'
+          when 256
+            'High(HARD)'
+          when 512
+            'Low(ORG)'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x1010 - FlashMode
+      #
+      class FlashMode < Base
+
+        def to_s
+          case @formatted
+          when 0
+            'Auto'
+          when 1
+            'On'
+          when 2
+            'Off'
+          when 3
+            'Red-eye reduction'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x1011 - FlashStrength
+      #
+      class FlashStrength < Base
+      end
+
+      #
+      # 0x1020 - Macro
+      #
+      class Macro < Base
+
+        def to_s
+          case @formatted
+          when 0
+            'Off'
+          when 1
+            'On'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x1021 - Focus mode
+      #
+      class FocusMode < Base
+
+        def to_s
+          case @formatted
+          when 0
+            'Auto focus'
+          when 1
+            'Manual focus'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x1030 - SlowSync
+      #
+      class SlowSync < Base
+
+        def to_s
+          case @formatted
+          when 0
+            'Off'
+          when 1
+            'On'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x1031 - PictureMode
+      #
+      class PictureMode < Base
+
+        def to_s
+          case @formatted
+          when 0
+            'Auto'
+          when 1
+            'Portrait scene'
+          when 2
+            'Landscape scene'
+          when 4
+            'Sports scene'
+          when 5
+            'Night scene'
+          when 6
+            'Program Auto Exposure'
+          when 256
+            'Aperture prior Auto Exposure'
+          when 512
+            'Shutter prior Auto Exposure'
+          when 768
+            'Manual exposure'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x1032 - Unknown
+      #
+
+      #
+      # 0x1100 - Cont_Bracket
+      #
+      class Cont_Bracket < Base
+
+        def to_s
+          case @formatted
+          when 0
+            'Off'
+          when 1
+            'On'
+          else
+            'Unknown'
+          end
+        end
+
+        def name
+          'Continuous/AutoBracket'
+        end
+
+      end
+
+      #
+      # 0x1200 - Unknown
+      #
+
+      #
+      # 0x1300 - Blur warning
+      #
+      class BlurWarning < Base
+
+        def to_s
+          case @formatted
+          when 0
+            'No blur warning'
+          when 1
+            'Blur warning'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x1301 - Focus warning
+      #
+      class FocusWarning < Base
+
+        def to_s
+          case @formatted
+          when 0
+            'Auto Focus good'
+          when 1
+            'Out of Focus'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x1302 - AE warning
+      #
+      class AutoExposureWarning < Base
+
+        def to_s
+          case @formatted
+          when 0
+            'Auto exposure good'
+          when 1
+            'Over exposure'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+    end
+
+    #
+    # Tags used in Fujifilm makernote
+    #
+    FujifilmIFDTable = {
+      0x0000 => MakerNote::Version,
+      0x1000 => MakerNote::Quality,
+      0x1001 => MakerNote::Sharpness,
+      0x1002 => MakerNote::White,
+      0x1003 => MakerNote::Color,
+      0x1004 => MakerNote::Tone,
+      0x1010 => MakerNote::FlashMode,
+      0x1011 => MakerNote::FlashStrength,
+      0x1020 => MakerNote::Macro,
+      0x1021 => MakerNote::FocusMode,
+      0x1030 => MakerNote::SlowSync,
+      0x1031 => MakerNote::PictureMode,
+      0x1032 => Unknown,
+      0x1100 => MakerNote::Cont_Bracket,
+      0x1200 => Unknown,
+      0x1300 => MakerNote::BlurWarning,
+      0x1301 => MakerNote::FocusWarning,
+      0x1302 => MakerNote::AutoExposureWarning
+    }
+
+  end
+
+  class Fujifilm
+
+    def initialize(fin, tiff_origin, dataPos, byteOrder_module)
+      @fin = fin
+      @tiffHeader0 = tiff_origin
+      @dataPos = dataPos
+      @byteOrder_module = Utils::Decode::Intel  # force Intel
+      self.extend @byteOrder_module
+    end
+
+    def scan_IFD
+      #
+      # Fujifilm MakerNote starts from 8 byte from the origin
+      #
+      @fin.pos = @dataPos + 8
+      offset = decode_ushort(fin_read_n(2))
+      @fin.pos = @dataPos + offset
+      #
+      # get the number of tags
+      #
+      numDirs = decode_ushort(fin_read_n(2))
+      #
+      # now scan them
+      #
+      1.upto(numDirs) {
+        curpos_tag = @fin.pos
+        tag = parseTagID(fin_read_n(2))
+        tagclass = Tag.find(tag.hex, Tag::FujifilmIFDTable)
+        unit, formatter = Tag::Format::Unit[decode_ushort(fin_read_n(2))]
+        count = decode_ulong(fin_read_n(4))
+        tagdata = fin_read_n(4)
+
+        obj = tagclass.new(tag, "MakerNote", count)
+        obj.extend formatter, @byteOrder_module
+        obj.pos = curpos_tag
+        if unit * count > 4
+          curpos = @fin.pos
+          begin
+            @fin.pos = @dataPos + decode_ulong(tagdata)
+            obj.dataPos = @fin.pos
+            obj.data = fin_read_n(unit*count)
+          ensure
+            @fin.pos = curpos
+          end
+        else
+          obj.dataPos = @fin.pos - 4
+          obj.data = tagdata
+        end
+        obj.processData
+        yield obj
+      }
+    end
+
+    private
+
+    def fin_read_n(n)
+      @fin.read(n)
+    end
+
+  end
+
+end
Index: /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/makernote/olympus.rb
===================================================================
--- /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/makernote/olympus.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/makernote/olympus.rb (revision 710)
@@ -0,0 +1,225 @@
+#
+#   exif/makernote/olympus.rb -
+#
+#   Copyright (C) Ryuichi Tamura (r-tam@fsinet.or.jp)
+#
+#   $Revision: 1.1.1.1 $
+#    $Date: 2002/12/16 07:59:00 $
+#
+#== Reference
+#
+#http://www.ba.wakwak.com/%7Etsuruzoh/Computer/Digicams/exif-e.html
+#
+require 'exifparser/tag'
+require 'exifparser/utils'
+
+module Exif
+
+  #
+  # Tags used in Olympus Makernote
+  #
+  module Tag
+
+    module MakerNote
+
+      #
+      # 0x0200 - SpecialMode
+      #
+      class SpecialMode < Base
+
+        def processData
+          @formatted = []
+          partition_data(@count) do |data|
+            @formatted.push _formatData(data)
+          end
+        end
+
+        def to_s
+          buf = "Picture taking mode: "
+          case @formatted[0]
+          when 0
+            buf << 'Normal,'
+          when 1
+            buf << 'Unknown,'
+          when 2
+            buf << 'Fast,'
+          when 3
+            buf << 'Panorama,'
+          else
+            buf << 'Unknown,'
+          end
+          buf << " Sequence number: #{@formatted[1]},"
+          buf << " Panorama direction: "
+          case @formatted[2]
+          when 1
+            buf << 'left to right'
+          when 2
+            buf << 'right to left'
+          when 3
+            buf << 'bottom to top'
+          when 4
+            buf << 'top to bottom'
+          else
+            buf << 'unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x0201 - JpegQual
+      #
+      class JpegQual < Base
+
+        def to_s
+          case @formatted
+          when 1
+            "Standard Quality"
+          when 2
+            "High Quality"
+          when 3
+            "Super High Quality"
+          else
+            "Unknown"
+          end
+        end
+
+      end
+
+      #
+      # 0x0202 - Macro
+      #
+      class Macro < Base
+
+        def to_s
+          case @formatted
+          when 0
+            'Off'
+          when 1
+            'On'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x0203 - Unknown
+      #
+
+      #
+      # 0x0204 - DigiZoom
+      #
+      class DigiZoom < Base
+      end
+
+      #
+      # 0x0205 - Unknown
+      #
+
+      #
+      # 0x0206 - Unknown
+      #
+
+      #
+      # 0x0207 - SoftwareRelease
+      #
+      class SoftwareRelease < Base
+      end
+
+      #
+      # 0x0208 - PictInfo
+      #
+      class PictInfo < Base
+      end
+
+      #
+      # 0x0209 - CameraID
+      #
+      class CameraID < Base
+      end
+
+      #
+      # 0x0f00
+      #
+      class DataDump < Base
+      end
+
+    end
+
+    OlympusIFDTable = {
+      0x0200 => MakerNote::SpecialMode,
+      0x0201 => MakerNote::JpegQual,
+      0x0202 => MakerNote::Macro,
+      0x0203 => Unknown,
+      0x0204 => MakerNote::DigiZoom,
+      0x0205 => Unknown,
+      0x0206 => Unknown,
+      0x0207 => MakerNote::SoftwareRelease,
+      0x0208 => MakerNote::PictInfo,
+      0x0209 => MakerNote::CameraID,
+      0x0f00 => MakerNote::DataDump
+    }
+
+  end
+
+  class Olympus
+
+    def initialize(fin, tiff_origin, dataPos, byteOrder_module)
+      @fin = fin
+      @tiffHeader0 = tiff_origin
+      @dataPos = dataPos
+      @byteOrder_module = byteOrder_module
+      self.extend @byteOrder_module
+    end
+
+    def scan_IFD
+      #
+      # Olympus MakerNote starts from 8 byte from the origin.
+      #
+      @fin.pos = @dataPos + 8
+      #
+      # get the number of tags
+      #
+      num_dirs = decode_ushort(fin_read_n(2))
+      #
+      # now scan them
+      #
+      1.upto(num_dirs) {
+        curpos_tag = @fin.pos
+        tag = parseTagID(fin_read_n(2))
+        tagclass = Tag.find(tag.hex, Tag::OlympusIFDTable)
+        unit, formatter = Tag::Format::Unit[decode_ushort(fin_read_n(2))]
+        count = decode_ulong(fin_read_n(4))
+        tagdata = fin_read_n(4)
+        obj = tagclass.new(tag, "MakerNote", count)
+        obj.extend formatter, @byteOrder_module
+        obj.pos = curpos_tag
+        if unit * count > 4
+          curpos = @fin.pos
+          begin
+            @fin.pos = @tiffHeader0 + decode_ulong(tagdata)
+            obj.dataPos = @fin.pos
+            obj.data = fin_read_n(unit*count)
+          ensure
+            @fin.pos = curpos
+          end
+        else
+          obj.dataPos = @fin.pos - 4
+          obj.data = tagdata
+        end
+        obj.processData
+        yield obj
+      }
+    end
+
+    private
+
+    def fin_read_n(n)
+      @fin.read(n)
+    end
+
+  end
+
+end
Index: /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/makernote/prove.rb
===================================================================
--- /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/makernote/prove.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/makernote/prove.rb (revision 710)
@@ -0,0 +1,77 @@
+#
+#
+#    exifparser/makernote/prove.rb -
+#
+#   Copyright (C) 2002 Ryuichi Tamura (r-tam@fsinet.or.jp)
+#
+#   $Revision: 1.1.1.1 $
+#   $Date: 2002/12/16 07:59:00 $
+#
+require 'exifparser/makernote/fujifilm'
+require 'exifparser/makernote/olympus'
+require 'exifparser/makernote/canon'
+require 'exifparser/makernote/nikon'
+require 'exifparser/makernote/nikon2'
+require 'exifparser/makernote/minolta'
+
+module Exif
+
+  module MakerNote
+
+    class NotSupportedError < RuntimeError; end
+
+    module_function
+
+    def prove(data, tag_make=nil, tag_model=nil)
+
+      make = tag_make == nil ? '' : tag_make.to_s.upcase
+      model = tag_model == nil ? '' : tag_model.to_s.upcase
+
+      #
+      # Identifier for OLYMPUS
+      #
+      if data[0..5] == "OLYMP\000"
+        return Olympus
+      #
+      # Identifier for FUJIFILM
+      #
+      elsif data[0..7] == "FUJIFILM"
+        return Fujifilm
+
+      #
+      # Identifier for Nikon
+      #
+
+      elsif make[0..4] == 'NIKON'
+        if data[0..5] == "Nikon\000"
+          if data[6] == 0x01 && data[7] == 0x00
+            return Nikon
+          end
+        end
+        return Nikon2
+
+      #
+      # Canon
+      #
+      elsif make[0..4] == 'CANON'
+        return Canon
+
+      #
+      # Minolta
+      #
+      elsif make[0..6] == 'MINOLTA'
+        return Minolta
+
+      end
+
+      #
+      # If none above is applied, raises exception,
+      # which will be caught by caller's rescue statement.
+      #
+      raise NotSupportedError
+    end
+    module_function :prove
+
+  end
+
+end
Index: /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/thumbnail.rb
===================================================================
--- /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/thumbnail.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/thumbnail.rb (revision 710)
@@ -0,0 +1,76 @@
+#
+#  exifparser/thumbnail.rb -
+#
+#  Copyright (C) 2002 Ryuichi Tamura (r-tam@fsinet.or.jp)
+#
+#  $Revision: 1.1.1.1 $
+#  $Date: 2002/12/16 07:59:00 $
+#
+
+module Exif
+
+  class Parser
+
+    alias orig_thumbnail thumbnail
+
+    #
+    # redefine method.
+    #
+    def thumbnail
+      Thumbnail.new(@result[:IFD1], @data)
+    end
+
+  end
+
+  #
+  # APIs are subject to change.
+  #
+  class Thumbnail
+
+    def initialize(ifd1, data)
+      @ifd1 = ifd1
+      @data = data
+    end
+
+    def size
+      @data.size
+    end
+
+    def write(dest)
+      dest << @data
+    end
+
+    def width
+      search_tag('ImageWidth')
+    end
+
+    def height
+      search_tag('ImageLength')
+    end
+    alias length height
+
+    def bits_per_sample
+      search_tag('BitsPerSample')
+    end
+
+    def compression
+      search_tag('Compression')
+    end
+
+    def photometric_interpretation
+      search_tag('PhotometricInterpretation')
+    end
+
+    def strip_offsets
+      search_tag('StripOffsets')
+    end
+
+    private
+
+    def search_tag(tag)
+      @ifd1.find { |t| t.name == tag }
+    end
+
+  end
+
+end
Index: /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/pre-setup.rb
===================================================================
--- /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/pre-setup.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/pre-setup.rb (revision 710)
@@ -0,0 +1,1 @@
+# pre-setup.rb - working with install.rb
Index: /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/scan.rb
===================================================================
--- /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/scan.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/scan.rb (revision 710)
@@ -0,0 +1,273 @@
+#
+#    exifparser/scan.rb
+#
+#    Copyright (C) 2002 Ryuichi Tamura (r-tam@fsinet.or.jp)
+#
+#    $Revision: 1.2 $
+#    $Date: 2003/04/20 19:58:31 $
+#
+#
+require 'exifparser/utils'
+require 'exifparser/tag'
+require 'exifparser/makernote/prove'
+
+module Exif
+
+  class Scanner
+
+    def initialize(fin)
+      @fin = fin.binmode
+      @result = {}
+      @tiffHeader0 = nil  # origin at which TIFF header begins
+      @byteOrder_module = nil
+    end
+    attr_reader :result
+
+    def finish
+      @fin.close
+    end
+
+    def scan
+      tic = Time.now if $DEBUG
+      #
+      # check soi (start of image)
+      #
+      @fin.pos = 0
+      unless @fin.readchar == "FF".hex and  @fin.readchar == "D8".hex
+        raise RuntimeError, 'not JPEG format'
+      end
+
+      #
+      # seek app1 (EXIF signature)
+      #
+      begin
+        marker = get_marker
+        break if (marker == 0xFFE1)
+        size = get_marker_datasize
+        @fin.seek(size - 2, IO::SEEK_CUR)
+      end while (!@fin.eof?)
+
+      if marker != 0xFFE1
+        raise RuntimeError, 'not EXIF format'
+      end
+
+      #
+      # get app1 Data size
+      #
+      @result[:app1DataSize] = get_marker_datasize()
+      curpos = @fin.pos
+      @result[:app1Data] = fin_read_n(@result[:app1DataSize])
+      @fin.pos = curpos
+
+      #
+      # EXIF header must be exactly "Exif\000\000", but some model
+      # does not provide correct one. So we relax the condition.
+      #
+      if (h = exif_identifier()) !~ /\AExif\000/
+        raise RuntimeError, "Invalid EXIF header: #{h}"
+      end
+
+      #
+      # examine TIFF header
+      #
+      @tiffHeader0, tiff_header = get_tiff_header()
+
+      #
+      # get byte order
+      #
+      case tiff_header[0,2]
+      when "MM"
+        @byteOrder_module = Utils::Decode::Motorola
+      when "II"
+        @byteOrder_module = Utils::Decode::Intel
+      else
+        raise RuntimeError, "Unknown byte order"
+      end
+      self.extend @byteOrder_module
+      @result[:offset_IFD0] = decode_ulong(tiff_header[4..-1])
+
+      #
+      # IFD0
+      #
+      @fin.pos = @tiffHeader0 + @result[:offset_IFD0]
+      @result[:IFD0] = []
+      scan_IFD(Tag::IFD0Table, Tag::IFD0Table.name) do |tag|
+        @result[:IFD0].push tag
+      end
+
+      #
+      # IFD1
+      #
+      @result[:IFD1] = []
+      next_ifd = decode_ulong(fin_read_n(4))
+      if next_ifd > 0
+        @fin.pos = @tiffHeader0 + next_ifd
+        scan_IFD(Tag::IFD1Table, Tag::IFD1Table.name) do |tag|
+          @result[:IFD1].push tag
+        end
+      end
+
+      #
+      # GPS IFD
+      #
+      @result[:GPS] = []
+      found = @result[:IFD0].find{ |e|
+        e.class == Tag::GPSIFDPointer
+      }
+      if found
+        @result[:offset_GPS] = found.processData
+        @fin.pos = @tiffHeader0 + @result[:offset_GPS]
+        scan_IFD(Tag::GPSIFDTable, Tag::GPSIFDTable.name) do |tag|
+          @result[:GPS].push tag
+        end
+      end
+
+      #
+      # Exif IFD
+      #
+      @result[:Exif] = []
+      found = @result[:IFD0].find{ |e|
+        e.class == Tag::ExifIFDPointer
+      }
+      if found
+        @result[:offset_Exif] = found.processData
+        @fin.pos = @tiffHeader0 + @result[:offset_Exif]
+        scan_IFD(Tag::ExifIFDTable, Tag::ExifIFDTable.name) do |tag|
+          @result[:Exif].push tag
+        end
+      end
+
+      #
+      # Interoperability subIFD
+      #
+      @result[:Interoperability] = []
+      found = @result[:Exif].find {|e|
+        e.class == Tag::InteroperabilityIFDPointer
+      }
+      if found
+        @result[:offset_InteroperabilityIFD] = found.processData
+        @fin.pos = @tiffHeader0 + @result[:offset_InteroperabilityIFD]
+        scan_IFD(Tag::InteroperabilityIFDTable, Tag::InteroperabilityIFDTable.name) do |tag|
+          @result[:Interoperability].push tag
+        end
+      end
+
+      #
+      # MakerNote subIFD
+      #
+      @result[:MakerNote]=[]
+      found = @result[:Exif].find {|e| e.class == Tag::Exif::MakerNote }
+      if (found)
+        begin
+          # Because some vendors do not put any identifier in the header,
+          # we try to find which model is by seeing Tag::TIFF::Make, Tag::TIFF::Model.
+          make = @result[:IFD0].find {|e| e.class == Tag::TIFF::Make}
+          model = @result[:IFD0].find {|e| e.class == Tag::TIFF::Model}
+          # prove the maker
+          makernote_class = Exif::MakerNote.prove(found.data, make, model)
+          # set file pointer to the position where the tag was found.
+          @fin.pos = found.pos
+          makernote = makernote_class.new(@fin, @tiffHeader0, found.dataPos, @byteOrder_module)
+          makernote.scan_IFD do |tag|
+            @result[:MakerNote].push tag
+          end
+        rescue MakerNote::NotSupportedError
+        rescue Exception # what to do?
+          if $DEBUG
+            raise $!
+          end
+        end
+      end
+
+      #
+      # get thumbnail
+      #
+      if !@result[:IFD1].empty?
+        format = @result[:IFD1].find do |e|
+          e.class == Tag::TIFF::Compression
+        end.value
+        unless format == 6
+          raise NotImplementedError, "Sorry, thumbnail of other than JPEG format is not supported."
+        end
+        thumbStart = @result[:IFD1].find do |e|
+          e.class == Exif::Tag::TIFF::JpegInterchangeFormat
+        end.value
+        thumbLen = @result[:IFD1].find do |e|
+          e.class == Exif::Tag::TIFF::JpegInterchangeFormatLength
+        end.value
+        @fin.pos = @tiffHeader0 + thumbStart
+        # check JPEG soi maker
+        unless @fin.readchar == "FF".hex and  @fin.readchar == "D8".hex
+          raise RuntimeError, 'not JPEG format'
+        end
+        @fin.pos = @fin.pos - 2
+        # now read thumbnail image
+        @result[:Thumbnail] = @fin.read(thumbLen)
+      end
+
+      # turn on if $DEBUG
+      toc = Time.now if $DEBUG
+      puts(sprintf("scan time: %1.4f sec.", toc-tic)) if $DEBUG
+    end
+
+    private
+
+    def fin_read_n(n)
+      @fin.read(n)
+    end
+
+    def scan_IFD(tagTable, ifdname)
+      num_dirs = decode_ushort(fin_read_n(2))
+      1.upto(num_dirs) {
+        curpos_tag = @fin.pos
+        tag = parseTagID(fin_read_n(2))
+        tagclass = Tag.find(tag.hex, tagTable)
+        unit, formatter = Tag::Format::Unit[decode_ushort(fin_read_n(2))]
+        count = decode_ulong(fin_read_n(4))
+        tagdata = fin_read_n(4)
+        obj = tagclass.new(tag, ifdname, count)
+        obj.extend formatter, @byteOrder_module
+        obj.pos = curpos_tag
+        if unit * count > 4
+          curpos = @fin.pos
+          begin
+            @fin.pos = @tiffHeader0 + decode_ulong(tagdata)
+            obj.dataPos = @fin.pos
+            obj.data = fin_read_n(unit*count)
+          ensure
+            @fin.pos = curpos
+          end
+        else
+          obj.dataPos = @fin.pos - 4
+          obj.data = tagdata
+        end
+        obj.processData
+        yield obj
+      }
+    end
+
+    def get_marker
+      (@fin.readchar) << 8 | (@fin.readchar)
+    end
+
+    def get_marker_datasize
+      (@fin.readchar) << 8 | (@fin.readchar)
+    end
+
+    def exif_identifier
+      @fin.read(6)
+    end
+
+    def get_tiff_header
+      pos = @fin.pos
+      [pos, fin_read_n(8)]
+    end
+
+    def eoi
+      @fin.seek(-2, IO::SEEK_END)
+      [@fin.readchar, @fin.readchar].pack("C*")
+    end
+
+  end
+
+end
Index: /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/tag.rb
===================================================================
--- /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/tag.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser/tag.rb (revision 710)
@@ -0,0 +1,2234 @@
+#
+#  exifparser/tag.rb
+#
+#  Copyright (C) 2002 Ryuichi Tamura (r-tam@fsinet.or.jp)
+#
+#  $Revision: 1.3 $
+#  $Date: 2003/04/27 14:02:39 $
+#
+require 'exifparser/utils'
+require 'rational'
+
+module Exif
+
+  module Error
+    class TagNotFound < StandardError; end
+  end
+
+  module Tag
+
+    #
+    # modules under this module provides '_formatData()' method,
+    # which is invoked in Exif::Tag::Base#processData().
+    #
+    module Formatter
+
+      #
+      # convert data to unsigned byte(1 byte) value.
+      #
+      module UByte
+
+        def format
+          'Unsigned byte'
+        end
+
+        def _formatData(data)
+          data[0]
+        end
+
+      end
+
+      #
+      # convert data to ASCII(1 byte) values.
+      #
+      module Ascii
+
+        def format
+          'Ascii'
+        end
+
+        def _formatData(data)
+          data.delete("\000")
+        end
+
+      end
+
+      #
+      # convert data to unsigned short(2 byte) value.
+      #
+      module UShort
+
+        def format
+          'Unsigned short'
+        end
+
+        def _formatData(data)
+          decode_ushort(data)
+        end
+
+      end
+
+      #
+      # convert data to unsigned long(4 byte) value.
+      #
+      module ULong
+
+        def format
+          'Unsigned long'
+        end
+
+        def _formatData(data)
+          decode_ulong(data)
+        end
+
+      end
+
+      #
+      # convert data to unsigned rational(4+4 byte) value,
+      # which in turn is converted to Rational object.
+      #
+      module URational
+
+        def format
+          'Unsigned rational'
+        end
+
+        def _formatData(data)
+          a = decode_ulong(data[0,4])
+          b = decode_ulong(data[4,4])
+          return Rational(0,1) if b == 0
+          Rational(a, b)
+        end
+
+      end
+
+      #
+      # convert data to some value by user-supplied method.
+      # the client code should implement 'convert(data)' method.
+      #
+      module Undefined
+
+        def format
+          'Undefined'
+        end
+
+        def _formatData(data)
+          self.respond_to?(:_format0) ? _format0(data) : data
+        end
+
+      end
+
+      #
+      # convert data to signed short value.
+      #
+      module SShort
+
+        def format
+          'Signed short'
+        end
+
+        def _formatData(data)
+          decode_sshort(data)
+        end
+
+      end
+
+      #
+      # convert data to unsigned long(4 byte) value.
+      #
+      module SLong
+
+        def format
+          'Signed long'
+        end
+
+        def _formatData(data)
+          decode_slong(data)
+        end
+      end
+
+      #
+      # convert data to signed rational (4+4 byte) value.
+      #
+      module SRational
+
+        def format
+          'Signed rational'
+        end
+
+        def _formatData(data)
+          a = decode_slong(data[0,4])
+          b = decode_slong(data[4,4])
+          return Rational(0,1) if b == 0
+          Rational(a, b)
+        end
+
+      end
+
+
+    end # module Formatter
+
+    #
+    # maps number to size of one unit and the
+    # corresponding formatter (defined below) module.
+    #
+    module Format
+
+      Unit = {
+        1 =>  [1, ::Exif::Tag::Formatter::UByte],
+        2 =>  [1, ::Exif::Tag::Formatter::Ascii],
+        3 =>  [2, ::Exif::Tag::Formatter::UShort],
+        4 =>  [4, ::Exif::Tag::Formatter::ULong],
+        5 =>  [8, ::Exif::Tag::Formatter::URational],
+        #6 =>  [1, ::Exif::Tag::Formatter::SByte],
+        7 =>  [1, ::Exif::Tag::Formatter::Undefined],
+        8 =>  [2, ::Exif::Tag::Formatter::SShort],
+        9 =>  [4, ::Exif::Tag::Formatter::SLong],
+        10 => [8, ::Exif::Tag::Formatter::SRational],
+        #11 => [4, Exif::Formatter::SFloat],
+        #12 => [8, Exif::Formatter::DFloat]
+      }
+
+    end
+
+    #
+    # The base class that specifies common operations for tag data.
+    # All the tag classes are derived from this, and client code
+    # shoude use the public methods as interface.
+    #
+    class Base
+      #
+      # the argument 'byteorder' is either :intel or :motorola.
+      # this is used when packing @data given after initialized.
+      #
+      def initialize(tagID, ifdname, count)
+        @tagID = tagID
+        @IFD = ifdname
+        @count = count
+        @data = nil
+        @formatted = nil
+        @pos = nil
+        @dataPos = nil
+      end
+      attr_writer :data
+      attr_accessor :pos, :dataPos
+      attr_reader :tagID, :IFD, :count
+
+      def processData
+        @formatted = _formatData(@data)
+      end
+
+      #
+      # return tag's value: simply returns @formatted as it is.
+      #
+      def value
+        @formatted
+      end
+
+      #
+      # String representation of tag's value
+      # this is the default method that simply
+      # sends Object#to_s to @formatted.
+      # Subclasses may override this so that
+      # it returns more human-readable form.
+      #
+      def to_s
+        @formatted.to_s
+      end
+
+      #
+      # return tag's name
+      #
+      def name
+        self.class.to_s.split("::")[-1]
+      end
+
+      #
+      # format focal length
+      #
+      def formatFocalLength(f)
+        if (f.abs < 10.0)
+          str = "%.1f"%[f]
+        else
+          str = "%.0f"%[f]
+        end
+        "#{str}mm"
+      end
+
+      #
+      # format exposure time
+      #
+      def formatExposureTime(ss)
+        rss = 1.0/ss
+        if (rss >= 3.0)
+          str = "1/%.0f"%[rss]
+        elsif (3.0 > rss && rss > 1.0)
+          str = "1/%.1f"%[rss]
+        elsif (ss == 1.0)
+          str = "1.0"
+        elsif (3.0 > ss && ss > 1.0)
+          str = "%.1f"%[ss]
+        else
+          str = "%.0f"%[ss]
+        end
+        "#{str}sec."
+      end
+
+      #
+      # format f number
+      #
+      def formatFNumber(f)
+        if (f.abs < 10.0)
+          str = "%.1f"%[f]
+        else
+          str = "%.0f"%[f]
+        end
+        "F#{str}"
+      end
+
+      if not $DEBUG
+
+      def inspect
+        sprintf("#<%s ID=0x%04x, IFD=\"%s\" Name=\"%s\", Format=\"%s\", Count=\"%d\", Value=\"%s\">", self.class, @tagID, @IFD, self.name, self.format, self.count, self.value)
+      end
+
+      end
+
+      private
+
+      def partition_data(count)
+        i = 0
+        bytes = @data.size / count
+        while @data[i]
+          yield @data[i..i+bytes-1]
+          i = i + bytes
+        end
+      end
+
+    end
+
+    ##
+    ## the class for any unknown tags in
+    ## IFD0, IFD1, GPSIFD, ExifIFD, InteroperabilityIFD
+    ##
+    class Unknown < Base
+    end
+
+
+    ##
+    ## tags specific to Exif IFD.
+    ## (see Exif standard 2.2 Section 4.6.3-A )
+    ##
+
+    #
+    # 0x8769 - ExifIFDPointer
+    #
+    class ExifIFDPointer < Base
+    end
+
+    #
+    # 0x8825 - GPSIFDPointer
+    #
+    class GPSIFDPointer < Base
+    end
+
+    #
+    # 0xa005 - InteroperabilityIFDPointer
+    #
+    class InteroperabilityIFDPointer < Base
+    end
+
+
+    ##
+    ## tags related TIFF Rev. 6.0 Attribute Information.
+    ## (see Exif standard 2.2 Section 4.6.4 Table 3)
+    ##
+
+    module TIFF
+
+      #
+      # 0x0100 - ImageWidth
+      #
+      class ImageWidth < Base
+      end
+
+      #
+      # 0x0101 - ImageLength
+      #
+      class ImageLength < Base
+      end
+
+      #
+      # 0x0102 - BitsPerSample
+      #
+      class BitsPerSample < Base
+
+        def processData
+          @formatted = []
+          partition_data(@count) do |data|
+            @formatted.push _formatData(data)
+          end
+        end
+
+        def to_s
+          @formatted.join(",")
+        end
+
+      end
+
+      #
+      # 0x0103 - Compression
+      #
+      class Compression < Base
+
+        def to_s
+          case @formatted
+          when 1
+            'uncompressed'
+          when 6
+            'JPEG compression'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x0106 - PhotometricInterpretation
+      #
+      class PhotometricInterpretation < Base
+
+        def to_s
+          case @formatted
+          when 2
+            'RGB'
+          when 6
+            'YCbCr'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x0111 - StripOffsets
+      #
+      class StripOffsets < Base
+      end
+
+      #
+      # 0x0112 - Orientation
+      #
+      class Orientation < Base
+      end
+
+      #
+      # 0x0115 - SamplePerPixel
+      #
+      class SamplesPerPixel < Base
+      end
+
+      #
+      # 0x0116 - RowsPerStrip
+      #
+      class RowsPerStrip < Base
+      end
+
+      #
+      # 0x0117 - StripByteCounts
+      #
+      class StripByteCounts < Base
+      end
+
+      #
+      # 0x011a - XResolution
+      #
+      class XResolution < Base
+      end
+
+      #
+      # 0x011b - YResolution
+      #
+      class YResolution < Base
+      end
+
+      #
+      # 0x011c - PlanarConfiguration
+      #
+      class PlanarConfiguration < Base
+
+        def to_s
+          case @formatted
+          when 1
+            'chunky format'
+          when 2
+            'planar format'
+          else
+            'unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x0128 - ResolutionUnit
+      #
+      class ResolutionUnit < Base
+
+        def to_s
+          case @formatted
+          when 2
+            'inch'
+          when 3
+            'centimeter'
+          else
+            'unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x0201 - JpegInterchangeFormat
+      #
+      class JpegInterchangeFormat < Base
+      end
+
+      #
+      # 0x0202 - JpegInterchangeFormatLength
+      #
+      class JpegInterchangeFormatLength < Base
+      end
+
+      #
+      # 0x0211 - YCbCrCoefficients
+      #
+      class YCbCrCoefficients < Base
+
+        def processData
+          @formatted = []
+          partition_data(@count) do |data|
+            @formatted.push _formatData(data)
+          end
+        end
+
+        def to_s
+          @formatted.join(",")
+        end
+
+      end
+
+      #
+      # 0x0212 - YCbCrSubSampling
+      #
+      class YCbCrSubSampling < Base
+
+        def processData
+          @formatted = []
+          partition_data(@count) do |data|
+            @formatted.push _formatData(data)
+          end
+        end
+
+        def to_s
+          case @formatted
+          when [2,1]
+            'YCbCr4:2:2'
+          when [2,2]
+            'YCbCr4:2:0'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x0213 - YCbCrPositioning
+      #
+      class YCbCrPositioning < Base
+
+        def to_s
+          case @formatted
+          when 1
+            'centered'
+          when 2
+            'co-sited'
+          else
+            'unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x0214 - ReferenceBlackWhite
+      #
+      class ReferenceBlackWhite < Base
+      end
+
+      #
+      # 0x010e - ImageDescription
+      #
+      class ImageDescription < Base
+      end
+
+      #
+      # 0x010f - Make
+      #
+      class Make < Base
+      end
+
+      #
+      # 0x0110 - Model
+      #
+      class Model < Base
+      end
+
+      #
+      # 0x0112 - Orientation
+      #
+      class Orientation < Base
+
+        def to_s
+          case @formatted
+          when 1
+            "top - left"
+          when 2
+            "top - right"
+          when 3
+            "bottom - right"
+          when 4
+            "bottom - left"
+          when 5
+            "left - top"
+          when 6
+            "right - top"
+          when 7
+            "right - bottom"
+          when 8
+            "left - bottom"
+          else
+            "unknown"
+          end
+        end
+
+      end
+
+      #
+      # 0x011a - XResolution
+      #
+      class XResolution < Base
+      end
+
+      #
+      # 0x011b - YResolution
+      #
+      class YResolution < Base
+      end
+
+      #
+      # 0x0128 - ResolutionUnit
+      #
+      class ResolutionUnit < Base
+
+        def to_s
+          case @formatted
+          when 1
+            "none"
+          when 2
+            "inch"
+          when 3
+            "centimeter"
+          else
+            "unknown"
+          end
+        end
+
+      end
+
+      #
+      # 0x012D - TransferFunction
+      #
+      class TransferFunction < Base
+      end
+
+      #
+      # 0x0131 - Software
+      #
+      class Software < Base
+      end
+
+      #
+      # 0x0132 - DateTime
+      #
+      class DateTime < Base
+      end
+
+      #
+      # 0x013B - Artist
+      #
+      class Artist < Base
+      end
+
+      #
+      # 0x013E - WhitePoint
+      #
+      class WhitePoint < Base
+
+        def processData
+          @formatted = []
+          partition_data(@count) do |data|
+            @formatted.push _formatData(data)
+          end
+        end
+
+        def to_s
+          @formatted.join(",")
+        end
+
+      end
+
+      #
+      # 0x013f - PrimaryChromaticities
+      #
+      class PrimaryChromaticities < Base
+
+        def processData
+          @formatted = []
+          partition_data(@count) do |data|
+            @formatted.push _formatData(data)
+          end
+        end
+
+        def to_s
+          @formatted.join(",")
+        end
+
+      end
+
+      #
+      # 0x0211 - YCbCrCoefficients
+      #
+      class YCbCrCoefficients < Base
+
+        def processData
+          @formatted = []
+          partition_data(@count) do |data|
+            @formatted.push _formatData(data)
+          end
+        end
+
+        def to_s
+          @formatted.join(",")
+        end
+
+      end
+
+      #
+      # 0x0213 - YCbCrPositioning
+      #
+      class YCbCrPositioning < Base
+      end
+
+      #
+      # 0x0214 - ReferenceBlackWhite
+      #
+      class ReferenceBlackWhite < Base
+
+        def processData
+          @formatted = []
+          partition_data(@count) do |data|
+            @formatted.push _formatData(data)
+          end
+        end
+
+        def to_s
+          @formatted.join(",")
+        end
+
+      end
+
+      #
+      # 0x8298 - Copyright
+      #
+      class Copyright < Base
+
+        def _format0(data)
+          data.inspect
+        end
+
+        def to_s
+          sep = @data.index(0)
+          photographer = @data[0..sep-1]
+          editor = @data[sep+1..-1]
+    "#{photographer} (Photographer) - #{editor} (Editor)"
+        end
+
+      end
+
+    end
+
+    ##
+    ## Exif IFD tags
+    ##
+
+    module Exif
+
+      #
+      # 0x829a - ExposureTime
+      #
+      class ExposureTime < Base
+
+        def to_s
+          formatExposureTime(@formatted.to_f)
+        end
+
+      end
+
+      #
+      # 0x829d - FNumber
+      #
+      class FNumber < Base
+
+        def to_s
+          formatFNumber(@formatted.to_f)
+        end
+
+      end
+
+      #
+      # 0x8822 - ExposureProgram
+      #
+      class ExposureProgram < Base
+
+        def to_s
+          case @formatted
+          when 0
+            "Not defined"
+          when 1
+            "Manual"
+          when 2
+            "Normal program"
+          when 3
+            "Aperture priority"
+          when 4
+            "Shutter priority"
+          when 5
+            "Creative program (biased toward depth of field)"
+          when 6
+            "Action program (biased toward fast shutter speed)"
+          when 7
+            "Portrait mode (for closeup photos with the background out of focus)"
+          when 8
+            "Landscape mode (for landscape photos with the background in focus)"
+          else
+            "Unknown"
+          end
+        end
+
+      end
+
+      #
+      # 0x8824 - SpectralSensitivity
+      #
+      class SpectralSensitivity < Base
+      end
+
+      #
+      # 0x8828 - OECF
+      #
+      class OECF < Base
+      end
+
+      #
+      # 0x8827 - ISOSpeedRatings
+      #
+      class ISOSpeedRatings < Base
+      end
+
+      #
+      # 0x9000 - ExifVersion
+      #
+      class ExifVersion < Base
+
+        def _format0(data)
+          data
+        end
+
+        def to_s
+          case @formatted
+          when "0200"
+            "Exif Version 2.0"
+          when "0210"
+            "Exif Version 2.1"
+          when "0220"
+            "Exif Version 2.2"
+          else
+            "Unknown Exif Version"
+          end
+        end
+
+      end
+
+      #
+      # 0x9003 - DateTimeOriginal
+      #
+      class DateTimeOriginal < Base
+      end
+
+      #
+      # 0x9004 - DateTimeDigitized
+      #
+      class DateTimeDigitized < Base
+      end
+
+      #
+      # 0x9101 - ComponentsConfiguration
+      #
+      class ComponentsConfiguration < Base
+
+        def _format0(data)
+          data.unpack("C*").collect{|e| e.to_i}
+        end
+
+        def to_s
+          case @formatted
+          when [0x04,0x05,0x06,0x00]
+            'RGB'
+          when [0x01,0x02,0x03,0x00]
+            'YCbCr'
+          end
+        end
+
+      end
+
+      #
+      # 0x9102 - CompressedBitsPerPixel
+      #
+      class CompressedBitsPerPixel < Base
+
+        def to_s
+          "%.1fbits/pixel"%[@formatted.to_f]
+        end
+
+      end
+
+        #
+        # 0x9201 - ShutterSpeedValue
+        #
+      class ShutterSpeedValue < Base
+
+        def to_s
+          formatExposureTime(1.0/(2.0**(@formatted.to_f)))
+        end
+
+      end
+
+      #
+      # 0x9202 - ApertureValue
+      #
+      class ApertureValue < Base
+
+        def to_s
+          formatFNumber(Math.sqrt(2.0)**(@formatted.to_f))
+        end
+
+      end
+
+      #
+      # 0x9203 - BrightnessValue
+      #
+      class BrightnessValue < Base
+
+        def to_s
+          "%+.1f"%[@formatted.to_f]
+        end
+
+      end
+
+      #
+      # 0x9204 - ExposureBiasValue
+      #
+      class ExposureBiasValue < Base
+
+        def to_s
+          "%+.1f"%[@formatted.to_f]
+        end
+
+      end
+
+      #
+      # 0x9205 - MaxApertureValue
+      #
+      class MaxApertureValue < Base
+
+        def to_s
+          "F%.01f"%[Math.sqrt(2.0)**(@formatted.to_f)]
+        end
+
+      end
+
+      #
+      # 0x9206 - SubjectDistance
+      #
+      class SubjectDistance < Base
+      end
+
+      #
+      # 0x9207 - MeteringMode
+      #
+      class MeteringMode < Base
+
+        def to_s
+          case @formatted
+          when 1
+            'Average'
+          when 2
+            'CenterWeightedAverage'
+          when 3
+            'Spot'
+          when 4
+            'MultiSpot'
+          when 5
+            'Pattern'
+          when 6
+            'Partial'
+          when 255
+            'other'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x9208 - LightSource
+      #
+      class LightSource < Base
+
+        def to_s
+          case @formatted
+          when 0
+            'Unknown'
+          when 1
+            'Daylight'
+          when 2
+            'Fluorescent'
+          when 3
+            'Tungsten'
+          when 4
+            'Flash'
+          when 9
+            'Fine weather'
+          when 10
+            'Croudy weather'
+          when 11
+            'Shade'
+          when 12
+            'Daylight fluorescent'
+          when 13
+            'Day white fluorescent'
+          when 14
+            'Cool white fluorescent'
+          when 15
+            'White fluorescent'
+          when 17
+            'Standard light A'
+          when 18
+            'Standard light B'
+          when 19
+            'Standard light C'
+          when 20
+            'D55'
+          when 21
+            'D65'
+          when 22
+            'D75'
+          when 23
+            'D50'
+          when 24
+            'ISO studio tungsten'
+          when 255
+            'other light source'
+          else
+            'reserved'
+          end
+        end
+
+      end
+
+      #
+      # 0x9209 - Flash
+      #
+      class Flash < Base
+
+        def to_s
+          case @formatted
+          when 0x0000
+            'Flash did not fire.'
+          when 0x0001
+            'Flash fired.'
+          when 0x0005
+            'Strobe return light not detected.'
+          when 0x0007
+            'Strobe return light detected.'
+          when 0x0009
+            'Flash fired, compulsory flash mode.'
+          when 0x000d
+            'Flash fired, compulsory flash mode, return light not detected.'
+          when 0x000f
+            'Flash fired, compulsory flash mode, return light detected.'
+          when 0x0010
+            'Flash did not fire, compulsory flash mode.'
+          when 0x0018
+            'Flash did not fire, auto mode.'
+          when 0x0019
+            'Flash fired, auto mode.'
+          when 0x001d
+            'Flash fired, auto mode, return light not detected.'
+          when 0x001f
+            'Flash fired, auto mode, return light detected.'
+          when 0x0020
+            'No flash function.'
+          when 0x0041
+            'Flash fired, red-eye reduction mode.'
+          when 0x0045
+            'Flash fired, red-eye reduction mode, return light not detected.'
+          when 0x0047
+            'Flash fired, red-eye reduction mode, return light detected.'
+          when 0x0049
+            'Flash fired, compulsory flash mode.'
+          when 0x004d
+            'Flash fired, compulsory flash mode, return light not detected.'
+          when 0x004f
+            'Flash fired, compulsory flash mode, return light detected.'
+          when 0x0059
+            'Flash fired, auto mode, red-eye reduction mode.'
+          when 0x005d
+            'Flash fired, auto mode, return light not detected, red-eye reduction mode.'
+          when 0x005f
+            'Flash fired, auto mode, return light detected, red-eye reduction mode.'
+          else
+            "reserved"
+          end
+        end
+
+      end
+
+      #
+      # 0x920a - FocalLength
+      #
+      class FocalLength < Base
+
+        def to_s
+          formatFocalLength(@formatted.to_f)
+        end
+
+      end
+
+      #
+      # 0x9214 - SubjectArea
+      #
+      class SubjectArea < Base
+
+        def processData
+          @formatted = []
+          partition_data(@count) do |data|
+            @formatted.push _formatData(data)
+          end
+        end
+
+        def to_s
+          case @count
+          when 2
+            "Coordinate - [%d, %d]"%[*@formatted]
+          when 3
+            "Circle - Center: [%d, %d], Diameter: %d"%[*@formatted]
+          when 4
+            "Rectanglar - Center: [%d, %d], Width: %d, Height: %d"%[*@formatted]
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x927c - MakerNote
+      #
+      class MakerNote < Base
+
+        def data
+          @data
+        end
+
+        def _format0(data)
+          @data
+        end
+
+        def to_s
+          sprintf("MakerNote data (%i bytes)", data.size)
+        end
+
+      end
+
+      #
+      # 0x9286 - UserComment
+      #
+      class UserComment < Base
+
+        def to_s
+          case @data[0..7]
+          # ASCII
+          when [0x41,0x53,0x43,0x49,0x49,0x0,0x0,0x0]
+            @data[8..-1].pack("C*")
+          # JIS
+          when [0x4a,0x59,0x53,0x0,0x0,0x0,0x0,0x0]
+            @data[8..-1].pack("C*")
+          # Unicode
+          when [0x55,0x4e,0x49,0x43,0x4f,0x44,0x45,0x0]
+            @data[8..-1].pack("U*")
+          when [0x0]*8
+            @data[8..-1].pack("C*")
+          else
+            "unknown"
+          end
+        end
+
+      end
+
+      #
+      # 0x9290 - SubsecTime < Base
+      #
+      class SubsecTime < Base
+      end
+
+      #
+      # 0x9291 - SubsecTimeOriginal < Base
+      #
+      class SubsecTimeOriginal < Base
+      end
+
+      #
+      # 0x9292 - SubsecTimeDigitized < Base
+      #
+      class SubsecTimeDigitized < Base
+      end
+
+      #
+      # 0xa000 - FlashPixVersion
+      #
+      class FlashPixVersion < Base
+        def _format0(data)
+          data
+        end
+
+        def to_s
+          case @formatted
+          when "0100"
+            "FlashPix Version 1.0"
+          else
+            "Unknown FlashPix Version"
+          end
+        end
+
+      end
+
+      #
+      # 0xa001 - ColorSpace
+      #
+      class ColorSpace < Base
+
+        def to_s
+          case @formatted
+          when 1
+            'sRGB'
+          when 65535
+            'Uncalibrated'
+          else
+            'Unknown: #{@formatted}'
+          end
+        end
+
+      end
+
+      #
+      # 0xa002 - PixelXDimension
+      #
+      class PixelXDimension < Base
+
+        def processData
+          case self.byte_order
+          when :intel
+            @formatted = decode_ushort(@data[0,2])
+          when :motorola
+            @formatted = decode_ushort(@data[2,2])
+          end
+        end
+
+      end
+
+      #
+      # 0xa003 - PixelYDimension
+      #
+      class PixelYDimension < Base
+
+        def processData
+          case self.byte_order
+          when :intel
+            @formatted = decode_ushort(@data[0,2])
+          when :motorola
+            @formatted = decode_ushort(@data[2,2])
+          end
+        end
+
+      end
+
+      #
+      # 0xa004 - RelatedSoundFile
+      #
+      class RelatedSoundFile < Base
+      end
+
+      #
+      # 0xa20b - FlashEnergy
+      #
+      class FlashEnergy < Base
+      end
+
+      #
+      # 0xa20c - SpatialFrequencyResponse
+      #
+      class SpatialFrequencyResponse < Base
+      end
+
+      #
+      # 0xa20e - FocalPlaneXResolution
+      #
+      class FocalPlaneXResolution < Base
+      end
+
+      #
+      # 0xa20f - FocalPlaneYResolution
+      #
+      class FocalPlaneYResolution < Base
+      end
+
+      #
+      # 0xa210 - FocalPlaneResolutionUnit
+      #
+      class FocalPlaneResolutionUnit < Base
+
+        def to_s
+          case @formatted
+          when 1
+            'No unit'
+          when 2
+            'Inch'
+          when 3
+            'Centimeter'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0xa214 - SubjectLocation
+      #
+      class SubjectLocation < Base
+
+        def processData
+          @formatted = []
+          partition_data(@count) do |data|
+            @formatted.push _formatData(data)
+          end
+        end
+
+        def to_s
+          "[%d, %d]"%[*@formatted]
+        end
+
+      end
+
+      #
+      # 0xa215 - ExposureIndex
+      #
+      class ExposureIndex < Base
+      end
+
+      #
+      # 0xa217 - SensingMethod
+      #
+      class SensingMethod < Base
+
+        def to_s
+          case @formatted
+          when 2
+            'One-chip color area sensor'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0xa300 - FileSource
+      #
+      class FileSource < Base
+        def _format0(data)
+          data[0]
+        end
+
+        def to_s
+          case @formatted
+          when 0x03
+            'Digital still camera'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0xa301 - SceneType
+      #
+      class SceneType < Base
+
+        def _format0(data)
+          data[0]
+        end
+
+        def to_s
+          case @formatted
+          when 0x01
+            'Directory photographed'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0xa302 - CFAPattern
+      #
+      class CFAPattern < Base
+      end
+
+      #
+      # 0xa401 - CustomRendered
+      #
+      class CustomRendered < Base
+        def to_s
+          case @formatted
+          when 0
+            'Normal process'
+          when 1
+            'Custom process'
+          else
+            'reserved'
+          end
+        end
+      end
+
+      #
+      # 0xa402 - ExposureMode
+      #
+      class ExposureMode < Base
+        def to_s
+          case @formatted
+          when 0
+            'Auto exposure'
+          when 1
+            'Manual exposure'
+          when 2
+            'Auto bracket'
+          else
+            'reserved'
+          end
+        end
+      end
+
+      #
+      # 0xa403 - WhiteBalance
+      #
+      class WhiteBalance < Base
+        def to_s
+          case @formatted
+          when 0
+            'Auto white balance'
+          when 1
+            'Manual white balance'
+          else
+            'reserved'
+          end
+        end
+      end
+
+      #
+      # 0xa404 - DigitalZoomRatio
+      #
+      class DigitalZoomRatio < Base
+        def to_s
+          n = @formatted.numerator
+          d = @formatted.denominator
+          n == 0 ? 'None' : "%.1f"%[n.to_f/d.to_f]
+        end
+      end
+
+      #
+      # 0xa405 - FocalLengthIn35mmFilm
+      #
+      class FocalLengthIn35mmFilm < Base
+        def to_s
+          @formatted == 0 ? 'Unknown' : formatFocalLength(@formatted)
+        end
+      end
+
+      #
+      # 0xa406 - SceneCaptureType
+      #
+      class SceneCaptureType < Base
+        def to_s
+          case @formatted
+          when 0
+            'Standard'
+          when 1
+            'Landscape'
+          when 2
+            'Portrait'
+          when 3
+            'Nigit scene'
+          else
+            'reserved'
+          end
+        end
+      end
+
+      #
+      # 0xa407 - GaincControl
+      #
+      class GainControl < Base
+        def to_s
+          case @formatted
+          when 0
+            'None'
+          when 1
+            'Low gain up'
+          when 2
+            'High gain up'
+          when 3
+            'Low gain down'
+          when 4
+            'High gain down'
+          else
+            'reserved'
+          end
+        end
+      end
+
+      #
+      # 0xa408 - Contrast
+      #
+      class Contrast < Base
+        def to_s
+          case @formatted
+          when 0
+            'Normal'
+          when 1
+            'Soft'
+          when 2
+            'Hard'
+          else
+            'reserved'
+          end
+        end
+      end
+
+      #
+      # 0xa409 - Saturation
+      #
+      class Saturation < Base
+        def to_s
+          case @formatted
+          when 0
+            'Normal'
+          when 1
+            'Low saturation'
+          when 2
+            'High saturation'
+          else
+            'reserved'
+          end
+        end
+      end
+
+      #
+      # 0xa40a - Sharpness
+      #
+      class Sharpness < Base
+        def to_s
+          case @formatted
+          when 0
+            'Normal'
+          when 1
+            'Soft'
+          when 2
+            'Hard'
+          else
+            'reserved'
+          end
+        end
+      end
+
+      #
+      # 0xa40b - DeviceSettingDescription
+      #
+      class DeviceSettingDescription < Base
+      end
+
+      #
+      # 0xa40c - SubjectDistanceRange
+      #
+      class SubjectDistanceRange < Base
+        def to_s
+          case @formatted
+          when 0
+            'Unknown'
+          when 1
+            'Macro'
+          when 2
+            'Close view'
+          when 3
+            'Distant view'
+          else
+            'reserved'
+          end
+        end
+      end
+
+      #
+      # 0xa420 - ImageUniqueID
+      #
+      class ImageUniqueID < Base
+      end
+
+    end
+
+    ##
+    ## GPS IFD tags
+    ##
+
+    module GPS
+
+      #
+      # 0x0000 - GPSVersionID
+      #
+      # type : byte
+      # count: 4
+      #
+      class GPSVersionID < Base
+
+        def to_s
+          case @formatted
+          when [2,0,0,0]
+            "Version 2.0"
+          else
+            "Unknown"
+          end
+        end
+
+      end
+
+      #
+      # 0x0001 - GPSLatitudeRef
+      #
+      class GPSLatitudeRef < Base
+
+        def to_s
+          case @formatted
+          when 'N'
+            'North latitude'
+          when 'S'
+            'South latitude'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x0002 - GPSLatitude
+      #
+      class GPSLatitude < Base
+
+        def processData
+          @formatted = []
+          partition_data(@count) do |data|
+            @formatted.push _formatData(data)
+          end
+        end
+
+        def to_s
+          @formatted.join(",")
+        end
+
+      end
+
+      #
+      # 0x0003 - GPSLongitudeRef
+      #
+      class GPSLongitudeRef < Base
+
+        def to_s
+          case @formatted
+          when 'E'
+            'East longitude'
+          when 'W'
+            'West longitude'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x0004 - GPSLongitude
+      #
+      class GPSLongitude < Base
+
+        def processData
+          @formatted = []
+          partition_data(@count) do |data|
+            @formatted.push _formatData(data)
+          end
+        end
+
+        def to_s
+          @formatted.join(",")
+        end
+
+      end
+
+      #
+      # 0x0005 - GPSAltitudeRef
+      #
+      class GPSAltitudeRef < Base
+
+        def to_s
+          case @formatted
+          when 0
+            'Sea level'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x0006 - GPSAltitude
+      #
+      class GPSAltitude < Base
+      end
+
+      #
+      # 0x0007 - GPSTimeStamp
+      #
+      class GPSTimeStamp < Base
+
+        def processData
+          @formatted = []
+          partition_data(@count) do |data|
+            @formatted.push _formatData(data)
+          end
+        end
+
+        def to_s
+          @formatted.join(",")
+        end
+
+      end
+
+      #
+      # 0x0008 - GPSSatelites
+      #
+      class GPSSatelites < Base
+      end
+
+      #
+      # 0x0009 -  GPSStatus
+      #
+      class GPSStatus < Base
+
+        def to_s
+          case @formatted
+          when 'A'
+            'Measurement in progress'
+          when 'V'
+            'Measurement in interoperability'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x000A - GPSMeasureMode
+      #
+      class GPSMeasureMode < Base
+
+        def to_s
+          case @formatted
+          when '2'
+            '2-dimensional measurement'
+          when '3'
+            '3-dimensional measurement'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x000B - GPSDOP
+      #
+      class GPSDOP < Base
+      end
+
+      #
+      # 0x000C - GPSSpeedRef
+      #
+      class GPSSpeedRef < Base
+
+        def to_s
+          case @formatted
+          when 'K'
+            'Kilometers per hour'
+          when 'M'
+            'Miles per hour'
+          when 'N'
+            'Knots'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x000D - GPSSpeed
+      #
+      class GPSSpeed < Base
+      end
+
+      #
+      # 0x000E - GPSTrackRef
+      #
+      class GPSTrackRef < Base
+
+        def to_s
+          case @formatted
+          when 'T'
+            'True direction'
+          when 'M'
+            'Magnetic direction'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x000F - GPSTrack
+      #
+      class GPSTrack < Base
+      end
+
+      #
+      # 0x0010 - GPSImgDirectionRef
+      #
+      class GPSImgDirectionRef < Base
+
+        def to_s
+          case @formatted
+          when 'T'
+            'True direction'
+          when 'M'
+            'Magnetic direction'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x0011 - GPSImgDirection
+      #
+      class GPSImgDirection < Base
+      end
+
+      #
+      # 0x0012 - GPSMapDatum
+      #
+      class GPSMapDatum < Base
+      end
+
+      #
+      # 0x0013 - GPSDestLatitudeRef
+      #
+      class GPSDestLatitudeRef < Base
+
+        def to_s
+          case @formatted
+          when 'N'
+            'North latitude'
+          when 'S'
+            'South latitude'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x0014 - GPSDestLatitude
+      #
+      class GPSDestLatitude < Base
+
+        def processData
+          @formatted = []
+          partition_data(@count) do |data|
+            @formatted.push _formatData(data)
+          end
+        end
+
+        def to_s
+          @formatted.join(",")
+        end
+
+      end
+
+      #
+      # 0x0015 - GPSDestLongitudeRef
+      #
+      class GPSDestLongitudeRef < Base
+
+        def to_s
+          case @formatted
+          when 'E'
+            'East longitude'
+          when 'W'
+            'West longitude'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x0016 - GPSDestLongitude
+      #
+      class GPSDestLongitude < Base
+
+        def processData
+          @formatted = []
+          partition_data(@count) do |data|
+            @formatted.push _formatData(data)
+          end
+        end
+
+        def to_s
+          @formatted.join(",")
+        end
+
+      end
+
+      #
+      # 0x0017 - GPSDestBearingRef
+      #
+      class GPSDestBearingRef < Base
+
+        def to_s
+          case @formatted
+          when 'T'
+            'True direction'
+          when 'M'
+            'Magnetic direction'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x0018 - GPSDestBearing
+      #
+      class GPSDestBearing < Base
+      end
+
+      #
+      # 0x0019 - GPSDestDistanceRef
+      #
+      class GPSDestDistanceRef < Base
+
+        def to_s
+          case @formatted
+          when 'K'
+            'Kilometers'
+          when 'M'
+            'Miles'
+          when 'N'
+            'Knots'
+          else
+            'Unknown'
+          end
+        end
+
+      end
+
+      #
+      # 0x001A
+      #
+      class GPSDestDistance < Base
+      end
+
+    end
+
+    ##
+    ## Interoperability IFD tags
+    ##
+
+    module Interoperability
+
+      #
+      # 0x0001 - InteroperabilityIndex
+      #
+      class InteroperabilityIndex < Base
+      end
+
+      #
+      # 0x0002 - InteroperabilityVersion
+      #
+      class InteroperabilityVersion < Base
+      end
+
+      #
+      # 0x1000 - RelatedImageFileFormat
+      #
+      class RelatedImageFileFormat
+      end
+
+      #
+      # 0x1001 - RelatedImageWidth
+      #
+      class RelatedImageWidth < Base
+      end
+
+      #
+      # 0x1002 - RelatedImageLength
+      #
+      class RelatedImageLength < Base
+      end
+
+    end
+
+
+    #
+    # Hash tables that maps tag ID to the corresponding class.
+    #
+
+    ExifSpecific = {
+      0x8769 => ExifIFDPointer,
+      0x8825 => GPSIFDPointer,
+      0xa005 => InteroperabilityIFDPointer
+    }
+
+    TIFFAttributes = {
+      0x0100 => TIFF::ImageWidth,
+      0x0101 => TIFF::ImageLength,
+      0x0102 => TIFF::BitsPerSample,
+      0x0103 => TIFF::Compression,
+      0x0106 => TIFF::PhotometricInterpretation,
+      0x010E => TIFF::ImageDescription,
+      0x010F => TIFF::Make,
+      0x0110 => TIFF::Model,
+      0x0111 => TIFF::StripOffsets,
+      0x0112 => TIFF::Orientation,
+      0x0115 => TIFF::SamplesPerPixel,
+      0x0116 => TIFF::RowsPerStrip,
+      0x0117 => TIFF::StripByteCounts,
+      0x011A => TIFF::XResolution,
+      0x011B => TIFF::YResolution,
+      0x011C => TIFF::PlanarConfiguration,
+      0x0128 => TIFF::ResolutionUnit,
+      0x012D => TIFF::TransferFunction,
+      0x0131 => TIFF::Software,
+      0x0132 => TIFF::DateTime,
+      0x013B => TIFF::Artist,
+      0x013E => TIFF::WhitePoint,
+      0x013F => TIFF::PrimaryChromaticities,
+      0x0201 => TIFF::JpegInterchangeFormat,
+      0x0202 => TIFF::JpegInterchangeFormatLength,
+      0x0211 => TIFF::YCbCrCoefficients,
+      0x0212 => TIFF::YCbCrSubSampling,
+      0x0213 => TIFF::YCbCrPositioning,
+      0x0214 => TIFF::ReferenceBlackWhite,
+      0x8298 => TIFF::Copyright,
+    }
+
+    #
+    # ExifStandard 2.2, Section 4.6.8-A
+    #
+    IFD0Table = TIFFAttributes.update ExifSpecific
+
+    def IFD0Table.name
+      "IFD0"
+    end
+
+    #
+    # ExifStandard 2.2, Section 4.6.8-B
+    #
+    IFD1Table = IFD0Table.dup
+
+    def IFD1Table.name
+      "IFD1"
+    end
+
+
+    ExifIFDTable = {
+      0x829a => Exif::ExposureTime,
+      0x829d => Exif::FNumber,
+      0x8822 => Exif::ExposureProgram,
+      0x8824 => Exif::SpectralSensitivity,
+      0x8827 => Exif::ISOSpeedRatings,
+      0x8828 => Exif::OECF,
+      0x9000 => Exif::ExifVersion,
+      0x9003 => Exif::DateTimeOriginal,
+      0x9004 => Exif::DateTimeDigitized,
+      0x9101 => Exif::ComponentsConfiguration,
+      0x9102 => Exif::CompressedBitsPerPixel,
+      0x9201 => Exif::ShutterSpeedValue,
+      0x9202 => Exif::ApertureValue,
+      0x9203 => Exif::BrightnessValue,
+      0x9204 => Exif::ExposureBiasValue,
+      0x9205 => Exif::MaxApertureValue,
+      0x9206 => Exif::SubjectDistance,
+      0x9207 => Exif::MeteringMode,
+      0x9208 => Exif::LightSource,
+      0x9209 => Exif::Flash,
+      0x920a => Exif::FocalLength,
+      0x9214 => Exif::SubjectArea,
+      0x927c => Exif::MakerNote,
+      0x9286 => Exif::UserComment,
+      0x9290 => Exif::SubsecTime,
+      0x9291 => Exif::SubsecTimeOriginal,
+      0x9292 => Exif::SubsecTimeDigitized,
+      0xa000 => Exif::FlashPixVersion,
+      0xa001 => Exif::ColorSpace,
+      0xa002 => Exif::PixelXDimension,
+      0xa003 => Exif::PixelYDimension,
+      0xa004 => Exif::RelatedSoundFile,
+      0xa005 => InteroperabilityIFDPointer,
+      0xa20b => Exif::FlashEnergy,
+      0xa20c => Exif::SpatialFrequencyResponse,
+      0xa20e => Exif::FocalPlaneXResolution,
+      0xa20f => Exif::FocalPlaneYResolution,
+      0xa210 => Exif::FocalPlaneResolutionUnit,
+      0xa214 => Exif::SubjectLocation,
+      0xa215 => Exif::ExposureIndex,
+      0xa217 => Exif::SensingMethod,
+      0xa300 => Exif::FileSource,
+      0xa301 => Exif::SceneType,
+      0xa302 => Exif::CFAPattern,
+      0xa401 => Exif::CustomRendered,
+      0xa402 => Exif::ExposureMode,
+      0xa403 => Exif::WhiteBalance,
+      0xa404 => Exif::DigitalZoomRatio,
+      0xa405 => Exif::FocalLengthIn35mmFilm,
+      0xa406 => Exif::SceneCaptureType,
+      0xa407 => Exif::GainControl,
+      0xa408 => Exif::Contrast,
+      0xa409 => Exif::Saturation,
+      0xa40a => Exif::Sharpness,
+      0xa40b => Exif::DeviceSettingDescription,
+      0xa40c => Exif::SubjectDistanceRange,
+      0xa420 => Exif::ImageUniqueID
+    }
+
+    def ExifIFDTable.name
+      "Exif"
+    end
+
+    GPSIFDTable = {
+      0x0000 => GPS::GPSVersionID,
+      0x0001 => GPS::GPSLatitudeRef,
+      0x0002 => GPS::GPSLatitude,
+      0x0003 => GPS::GPSLongitudeRef,
+      0x0004 => GPS::GPSLongitude,
+      0x0005 => GPS::GPSAltitudeRef,
+      0x0006 => GPS::GPSAltitude,
+      0x0007 => GPS::GPSTimeStamp,
+      0x0008 => GPS::GPSSatelites,
+      0x000a => GPS::GPSMeasureMode,
+      0x000b => GPS::GPSDOP,
+      0x000c => GPS::GPSSpeedRef,
+      0x000d => GPS::GPSSpeed,
+      0x000e => GPS::GPSTrackRef,
+      0x000f => GPS::GPSTrack,
+      0x0010 => GPS::GPSImgDirectionRef,
+      0x0011 => GPS::GPSImgDirection,
+      0x0012 => GPS::GPSMapDatum,
+      0x0013 => GPS::GPSDestLatitudeRef,
+      0x0014 => GPS::GPSDestLatitude,
+      0x0015 => GPS::GPSDestLongitudeRef,
+      0x0016 => GPS::GPSDestLongitude,
+      0x0017 => GPS::GPSDestBearingRef,
+      0x0018 => GPS::GPSDestBearing,
+      0x0019 => GPS::GPSDestDistanceRef,
+      0x001A => GPS::GPSDestDistance
+    }
+
+    def GPSIFDTable.name
+      "GPS"
+    end
+
+    InteroperabilityIFDTable = {
+      0x0001 => Interoperability::InteroperabilityIndex,
+      0x0002 => Interoperability::InteroperabilityVersion,
+      0x1000 => Interoperability::RelatedImageFileFormat,
+      0x1001 => Interoperability::RelatedImageWidth,
+      0x1002 => Interoperability::RelatedImageLength
+    }
+
+    def InteroperabilityIFDTable.name
+      "Interoperability"
+    end
+
+
+    module_function
+
+    def find(tagid, table)
+      table[tagid] or ::Exif::Tag::Unknown
+    end
+
+  end
+
+end
Index: /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser.rb
===================================================================
--- /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/lib/exifparser/lib/exifparser.rb (revision 710)
@@ -0,0 +1,264 @@
+#
+#
+#=  exifparser.rb - Exif tag parser written in pure ruby
+#
+#Author:: Ryuchi Tamura (r-tam@fsinet.or.jp)
+#Copyright:: Copyright (C) 2002 Ryuichi Tamura.
+#
+# $Id: exifparser.rb,v 1.1.1.1 2002/12/16 07:59:00 tam Exp $
+#
+#== INTRODUCTION
+#
+#There are 2 classes you work with. ExifParser class is
+#the Exif tag parser that parses all tags defined EXIF-2.2 standard,
+#and many of extension tags uniquely defined by some digital equipment
+#manufacturers. Currently, part of tags defined by FujiFilm, and
+#Olympus is supported. After initialized with the path to image file
+#of EXIF format, ExifParser will provides which tags are available in
+#the image, and how you work with them.
+#
+#Tags availble from ExifParser is objects defined under Exif::Tag module,
+#with its name being the class name. For example if you get "Make" tag
+#from ExifParser, it is Exif::Tag::DateTime object. Inspecting it looks
+#following:
+#
+# #<Exif::Tag::TIFF::Make ID=0x010f, IFD="IFD0" Name="Make", Format="Ascii" Value="FUJIFILM">
+#
+#here, ID is Tag ID defined in EXIF-2.2 standard, IFD is the name of
+#Image File Directory, Name is String representation of tag ID, Format is
+#string that shows how the data is formatted, and Value is the value of
+#the tag. This is retrieved by Exif::Tag::Make#value.
+#
+#Another example. If you want to know whether flash was fired when the image
+#was generated, ExifParser returns Exif::Tag::Flash object:
+#
+# tag = exif['Flash']
+# p tag
+# => #<Exif::Tag::Exif::Flash ID=0x9209, IFD="Exif" Name="Flash", Format="Unsigned short" Value="1">
+# p tag.value
+# => 1
+#
+#It may happen that diffrent IFDs have the same tag name. In this case,
+#use Exif#tag(tagname, IFD)
+#
+#The value of the tag above, 1, is not clearly understood
+#(supposed to be 'true', though). Exif::Tag::Flash#to_s will provides
+#more human-readable form as String.
+#
+# tag.to_s #=> "Flash fired."
+#
+#many of these sentences are cited from Exif-2.2 standard.
+#
+#== USAGE
+# require 'exifparser'
+#
+# exif = ExifParser.new("fujifilm.jpg")
+#
+# 1. get a tag value by its name('Make') or its ID (0x010f)
+# exif['Make'] #=> 'FUJIFILM'
+# exif[0x010f] #=> 'FUJIFILM'
+#
+# if the specified tag is not found, nil is returned.
+#
+# 2. to see the image has the value of specified tag
+# exif.tag?('DateTime') #=> true
+# exif.tag?('CameraID') #=> false
+#
+# 3. get all the tags contained in the image.
+#
+# exif.tags
+#
+# or, if you want to know all the tags defined in specific IFD,
+#
+# exif.tags(:IFD0) # get all the tags defined in IFD0
+#
+# you can traverse each tag and work on it.
+#
+# exif.each do |tag|
+#   p tag.to_s
+# end
+#
+# # each tag in IFD0
+# exif.each(:IFD0) do |ifd0_tag|
+#  p ifd0_tag.to_s
+# end
+#
+# 4. extract thumbnail
+#
+# File.open("thumb.jpg") do |dest|
+#   exif.thumbnail dest
+# end
+#
+# dest object must respond to '<<'.
+#
+require 'exifparser/scan'
+
+module Exif
+
+  class Parser
+    #
+    # create a new object. fpath is String.
+    #
+    def initialize(fpath)
+      @fpath = fpath
+      @scanner = nil
+      File.open(fpath, "rb") do |f|
+        @scanner = Exif::Scanner.new(f)
+        @scanner.scan
+      end
+      @IFD0 = @scanner.result[:IFD0]
+      @IFD1 = @scanner.result[:IFD1]
+      @Exif = @scanner.result[:Exif]
+      @GPS  = @scanner.result[:GPS]
+      @Interoperability = @scanner.result[:Interoperability]
+      @MakerNote = @scanner.result[:MakerNote]
+      @thumbnail = @scanner.result[:Thumbnail]
+    end
+
+    def inspect
+      sprintf("#<%s filename=\"%s\" entries: IFD0(%d) IFD1(%d) Exif(%d) GPS(%d) Interoperability(%d) MakerNote(%d)>", self.class, @fpath, @IFD0.length, @IFD1.length, @Exif.length, @GPS.length, @Interoperability.length, @MakerNote.length)
+    end
+
+    #
+    # return true if specified tagid is defined or has some value.
+    #
+    def tag?(tagid)
+      search_tag(tagid) ? true : false
+    end
+
+    #
+    # search tag on the specific IFD
+    #
+    def tag(tagname, ifd=nil)
+      search_tag(tagname, ifd)
+    end
+
+    #
+    # search the specified tag values. return value is object of
+    # classes defined under Exif::Tag module.
+    #
+    def [](tagname)
+      self.tag(tagname)
+    end
+
+    #
+    # set the specified tag to the specified value.
+    # XXX NOT IMPLEMETED XXX
+    #
+    def []=(tag, value)
+      # not implemented
+    end
+
+    #
+    # extract the thumbnail image to dest. dest should respond to
+    # '<<' method.
+    #
+    def thumbnail(dest)
+      dest << @thumbnail
+    end
+
+    #
+    # return the size of the thumbnail image
+    #
+    def thumbnail_size
+      @thumbnail.size
+    end
+
+    #
+    # return all the tags in the image.
+    #
+    # if argument ifd is specified, every tags defined in the
+    # specified IFD are passed to block.
+    #
+    # return value is object of classes defined under Exif::Tag module.
+    #
+    # allowable arguments are:
+    # * :IFD0
+    # * :IFD1
+    # * :Exif
+    # * :GPS
+    # * :Interoperability
+    # * :MakerNote (if exist)
+    def tags(ifd=nil)
+      if ifd
+        @scanner.result[ifd]
+      else
+        [
+          @IFD0,
+          @IFD1,
+          @Exif,
+          @GPS,
+          @Interoperability,
+          @MakerNote
+        ].flatten
+      end
+    end
+
+    #
+    # execute given block with block argument being every tags defined
+    # in all the IFDs contained in the image.
+    #
+    # if argument ifd is specified, every tags defined in the
+    # specified IFD are passed to block.
+    #
+    # return value is object of classes defined under Exif::Tag module.
+    #
+    # allowable arguments are:
+    # * :IFD0
+    # * :IFD1
+    # * :Exif
+    # * :GPS
+    # * :Interoperability
+    # * :MakerNote
+    def each(ifd=nil)
+      if ifd
+        @scanner.result[ifd].each{ |tag| yield tag }
+      else
+        [
+          @IfD0,
+          @IFD1,
+          @Exif,
+          @Interoperability,
+          @MakerNote
+        ].flatten.each do |tag|
+          yield tag
+        end
+      end
+    end
+
+    private
+
+    def search_tag(tagID, ifd=nil)
+      if ifd
+        @scanner.result(ifd).find do |tag|
+          case tagID
+          when Fixnum
+            tag.tagID.hex == tagID
+          when String
+            tag.name == tagID
+          end
+        end
+      else
+        [
+          @IFD0,
+          @IFD1,
+          @Exif,
+          @GPS,
+          @Interoperability,
+          @MakerNote
+        ].flatten.find do |tag|
+          case tagID
+          when Fixnum
+            tag.tagID.hex == tagID
+          when String
+            tag.name == tagID
+          end
+        end
+      end
+    end
+
+  end # module Parser
+
+end # module Exif
+
+ExifParser = Exif::Parser
Index: /tdiary/branches/upstream/contrib/lib/exifparser/ChangeLog
===================================================================
--- /tdiary/branches/upstream/contrib/lib/exifparser/ChangeLog (revision 710)
+++ /tdiary/branches/upstream/contrib/lib/exifparser/ChangeLog (revision 710)
@@ -0,0 +1,169 @@
+2008-01-17 kp <kp@mmho.no-ip.org>
+
+	* lib/exifparser/tag.rb:
+	  Fix Rational method call for Ruby1.8.x
+
+Thu Dec 12 16:21:39 2002 Ryuichi Tamura <r-tam@fsinet.or.jp>
+
+	* lib/exifparser/tag.rb (Exif::Tag::Exif::LightSource#to_s):
+	  complete missing returned values that are introduced in
+	  Exif standard 2.2.
+
+Thu Dec 12 04:05:42 2002 Ryuichi Tamura <r-tam@fsinet.or.jp>
+
+	* lib/exifparser/tag.rb: EXIF tag set should include
+	  'InteroperabilityIFDPointer'
+
+Tue Dec 10 23:26:46 2002 Ryuichi Tamura <r-tam@fsinet.or.jp>
+
+	* lib/exifparser/scan.rb, lib/exifparser/tag.rb,
+	  lib/exifparser/makernote/*.rb: namespaces introduced to
+	  prevent name collision.
+
+Tue Dec 10 21:43:13 2002 Ryuichi Tamura <r-tam@fsinet.or.jp>
+
+	* lib/exifparser/*.rb, lib/exifparser/makernote/*.rb:
+	  fin_read_n() now simply returns byte stream, not pack("C*")'ed.
+	  use String#unpack, Array#unpack to decode stream (see utils.rb).
+	  All the class/routines that use input stream changed accordingly.
+
+Sun Dec  8 19:01:13 2002 Ryuichi Tamura <r-tam@fsinet.or.jp>
+
+	* lib/exifparser/tag.rb (Exif::Tag): missing tag classes
+	  completed to make conformable with Exif Standard 2.2
+	  (backward compatible newest standard).
+	  (Exif::Tag::Flash#to_s): returned value make comformable
+	  to Exif Standard 2.2.
+	  (Exif::Tag::PixelXDimension, Exif::Tag::PixelYDimension):
+	  renamed from 'ExifImageWidth' and 'ExifImageLength'.
+
+Sun Dec  8 15:27:27 2002 Ryuichi Tamura <r-tam@fsinet.or.jp>
+
+	* Apply following fixes/contributions by Noguchi Shingo.
+
+	* lib/exifparser/makernote/nikon2.rb,
+	  lib/exifparser/makernote/minolta.rb: new files.
+
+	* lib/exifparser/scan.rb (Exif::Scanner#scan): Tag::Model object
+	  was generated by Tag::Model. This bug is fixed by Noguchi Shingo.
+	  (Exif::Scanner#scan): use @byteOrder_module to instantiate
+	  makernote object. In many models, the byteorder of Makernote
+	  seems the same as that of IFDs.
+
+	* lib/exifparser/makernote/*.rb (Exif::Makernote::XXX#initialize):
+	  ditto.
+
+	* lib/exifparser/makernote/nikon.rb: new tag classes introduced,
+	  some of them are fixed.
+
+	* lib/exifparser/makernote/prove.rb: added new conditionals to
+	  return newly introduced models (Nikon, Nikon2, Minolta).
+
+	* lib/exifparser/tag.rb (Exif::Tag::Makernote#_format0,
+	  Exif::Tag::Makernote#to_s): the functionality of these methods
+	  should be exchanged.
+
+	* lib/exifparser/tag.rb (Exif::Tag::UserComment#to_s):
+	  wrong pack parameter.
+
+	* lib/exifparser/tag.rb (Exif::Tag::CustomRendered): new tag class.
+
+	* lib/exifparser/tag.rb (Exif::Tag::DigitalZoonRation): ditto.
+
+	* lib/exifparser/tag.rb (Exif::Tag::FocalLengthIn35mmFilm): ditto.
+
+	* lib/exifparser/tag.rb (Exif::Tag::SceneCaptureType): ditto.
+
+	* lib/exifparser/tag.rb (Exif::Tag::GainControl): ditto.
+
+	* lib/exifparser/tag.rb (Exif::Tag::Contrast): ditto.
+
+	* lib/exifparser/tag.rb (Exif::Tag::Saturation): ditto.
+
+	* lib/exifparser/tag.rb (Exif::Tag::Sharpness): ditto.
+
+	* lib/exifparser/tag.rb (Exif::Tag::DeviceSettingDescription): ditto.
+
+	* lib/exifparser/tag.rb (Exif::Tag::SubjectDistanceRange): ditto.
+
+Wed Nov 20 13:28:16 2002 Ryuichi Tamura <r-tam@fsinet.or.jp>
+
+	* lib/exifparser/utils.rb: Utils::Pack::Motorola,
+	  Utils::Pack::Intel: new modules. these are extended to
+	  objects that require decode data.
+
+	* lib/exifparser/scan.rb: __byteOrder__() is obsoleted.
+	  objects that require decode data will extend appropriate
+	  decode modules according to the byte order.
+	  (Exif::Scanner#scan_IFD): ditto.
+	  (Exif::Scanner#scan): check condition for valid EXIF
+	  identifier is relaxed because some model does not provide
+	  the correct one.
+
+	* lib/exifparser/tag.rb (Exif::Tag::Format): now returns
+	  a pair of sizeof(format) and formatter module.
+	  (Exif::Tag::Formatter): modules provide 'format' method that
+	  returns its name as string.
+	  (Exif::Tag::Base#initialize): does not require byteorder
+	  argument. needs count information instead.
+	  (Exif::Tag::Base#inspect): now shows format information.
+	  (Exif::Tag::Base#__byteOrder__): obsoleted. decode modules
+	  will be extended instead(see above).
+	  (Exif::Tag::XXX): Tag classes now do not include format
+	  modules in advance. these modules will extended according to
+	  the value of 'format' field of the data.
+
+	* lib/exifparser/makernote/fujifilm.rb,
+	  lib/exifparser/makernote/nikon.rb,
+	  lib/exifparser/makernote/canon.rb,
+	  lib/exifparser/makernote/olympus.rb: applies the same changes
+	  as described above.
+
+Wed Nov 13 18:59:08 2002 Ryuichi Tamura <r-tam@fsinet.or.jp>
+
+	* lib/exifparser/tag.rb (Exif::Tag::ExifVersion#to_s): fixed typo.
+
+Tue Nov 12 16:34:00 2002 Ryuichi Tamura <r-tam@fsinet.or.jp>
+
+	* lib/exifparser/scan.rb (Exif::Scanner#scan): some maker does not
+	  provide any identifier. So pass Exif::Tag::Make object to
+	  MakerNote.prove(), and use Exif::Tag::Make.value to prove the maker.
+	  (Exif::Scanner#get_app1_datasize): wrong shift length.
+
+	* lib/exifparser/makernote/prove.rb: ditto.
+
+	* lib/exifparser/makernote/prove.rb: added Canon makernote parsing
+	  class.
+
+2002-11-11 15:52  tam
+
+	* lib/exifparser/methods.rb: removed
+
+2002-11-11 15:49  tam
+
+	* lib/exifparser/tag.rb: Exif::Tag::ExifImageWidth,
+	  Exif::Tag::ExifImageLength - added processData() to workaround.
+	  Exif::Tag::Base#to_name: should not use Module#nesting. revert to
+	  original behaviour.
+
+2002-11-11 14:48  tam
+
+	* lib/exifparser/: tag.rb, thumbnail.rb: tag.rb
+	  (Exif::Tag::ExifImageLength, Exif::Tag::ExifImageHeight): should
+	  include Formatter::UShort.
+
+2002-11-11 12:21  tam
+
+	* lib/: exifparser.rb, exifparser/methods.rb, exifparser/tag.rb:
+	  exifparser/tag.rb (Exif::Tag::Base#name): use
+	  Module#module_nesting.
+
+2002-11-10 22:28  tam
+
+	* lib/: exifparser.rb, exifparser/scan.rb, exifparser/tag.rb,
+	  exifparser/makernote/olympus.rb: exifparser/tag.rb,
+	  exifparser/scan.rb: apply patches by noguchi
+	  shingo(noguchi@daifukuya.com) to fix bugs, inadequate tag
+	  representation (tag#to_s).  exifparser/makernote/olympus.rb: tag
+	  'CameraID' should be packed "C*" when to_s'ed.
+
Index: /tdiary/branches/upstream/contrib/lib/exifparser/install.rb
===================================================================
--- /tdiary/branches/upstream/contrib/lib/exifparser/install.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/lib/exifparser/install.rb (revision 710)
@@ -0,0 +1,1015 @@
+#
+# This file is automatically generated. DO NOT MODIFY!
+#
+# install.rb
+#
+#   Copyright (c) 2000-2002 Minero Aoki <aamine@loveruby.net>
+#
+#   This program is free software.
+#   You can distribute/modify this program under the terms of
+#   the GNU Lesser General Public License version 2.
+#
+
+### begin compat.rb
+
+unless Enumerable.instance_methods.include? 'inject' then
+module Enumerable
+  def inject( result )
+    each do |i|
+      result = yield(result, i)
+    end
+    result
+  end
+end
+end
+
+def File.read_all( fname )
+  File.open(fname, 'rb') {|f| return f.read }
+end
+
+def File.write( fname, str )
+  File.open(fname, 'wb') {|f| f.write str }
+end
+
+### end compat.rb
+### begin config.rb
+
+if i = ARGV.index(/\A--rbconfig=/) then
+  file = $'
+  ARGV.delete_at(i)
+  require file
+else
+  require 'rbconfig'
+end
+
+
+class ConfigTable
+
+  c = ::Config::CONFIG
+
+  rubypath = c['bindir'] + '/' + c['ruby_install_name']
+
+  major = c['MAJOR'].to_i
+  minor = c['MINOR'].to_i
+  teeny = c['TEENY'].to_i
+  version = "#{major}.#{minor}"
+
+  # ruby ver. >= 1.4.4?
+  newpath_p = ((major >= 2) or
+               ((major == 1) and
+                ((minor >= 5) or
+                 ((minor == 4) and (teeny >= 4)))))
+  
+  re = Regexp.new('\A' + Regexp.quote(c['prefix']))
+  subprefix = lambda {|path|
+      re === path and path.sub(re, '$prefix')
+  }
+
+  if c['rubylibdir'] then
+    # 1.6.3 < V
+    stdruby    = subprefix.call(c['rubylibdir'])
+    siteruby   = subprefix.call(c['sitedir'])
+    versite    = subprefix.call(c['sitelibdir'])
+    sodir      = subprefix.call(c['sitearchdir'])
+  elsif newpath_p then
+    # 1.4.4 <= V <= 1.6.3
+    stdruby    = "$prefix/lib/ruby/#{version}"
+    siteruby   = subprefix.call(c['sitedir'])
+    versite    = siteruby + '/' + version
+    sodir      = "$site-ruby/#{c['arch']}"
+  else
+    # V < 1.4.4
+    stdruby    = "$prefix/lib/ruby/#{version}"
+    siteruby   = "$prefix/lib/ruby/#{version}/site_ruby"
+    versite    = siteruby
+    sodir      = "$site-ruby/#{c['arch']}"
+  end
+
+  DESCRIPTER = [
+    [ 'prefix',    [ c['prefix'],
+                     'path',
+                     'path prefix of target environment' ] ],
+    [ 'std-ruby',  [ stdruby,
+                     'path',
+                     'the directory for standard ruby libraries' ] ],
+    [ 'site-ruby-common', [ siteruby,
+                     'path',
+                     'the directory for version-independent non-standard ruby libraries' ] ],
+    [ 'site-ruby', [ versite,
+                     'path',
+                     'the directory for non-standard ruby libraries' ] ],
+    [ 'bin-dir',   [ '$prefix/bin',
+                     'path',
+                     'the directory for commands' ] ],
+    [ 'rb-dir',    [ '$site-ruby',
+                     'path',
+                     'the directory for ruby scripts' ] ],
+    [ 'so-dir',    [ sodir,
+                     'path',
+                     'the directory for ruby extentions' ] ],
+    [ 'data-dir',  [ '$prefix/share',
+                     'path',
+                     'the directory for shared data' ] ],
+    [ 'ruby-path', [ rubypath,
+                     'path',
+                     'path to set to #! line' ] ],
+    [ 'ruby-prog', [ rubypath,
+                     'name',
+                     'the ruby program using for installation' ] ],
+    [ 'make-prog', [ 'make',
+                     'name',
+                     'the make program to compile ruby extentions' ] ],
+    [ 'without-ext', [ 'no',
+                       'yes/no',
+                       'does not compile/install ruby extentions' ] ]
+  ]
+
+  SAVE_FILE = 'config.save'
+
+  def ConfigTable.each_name( &block )
+    keys().each( &block )
+  end
+
+  def ConfigTable.keys
+    DESCRIPTER.collect {|k,*dummy| k }
+  end
+
+  def ConfigTable.each_definition( &block )
+    DESCRIPTER.each( &block )
+  end
+
+  def ConfigTable.get_entry( name )
+    name, ent = DESCRIPTER.assoc(name)
+    ent
+  end
+
+  def ConfigTable.get_entry!( name )
+    get_entry(name) or raise ArgumentError, "no such config: #{name}"
+  end
+
+  def ConfigTable.add_entry( name, vals )
+    ConfigTable::DESCRIPTER.push [name,vals]
+  end
+
+  def ConfigTable.remove_entry( name )
+    get_entry name or raise ArgumentError, "no such config: #{name}"
+    DESCRIPTER.delete_if {|n,arr| n == name }
+  end
+
+  def ConfigTable.config_key?( name )
+    get_entry(name) ? true : false
+  end
+
+  def ConfigTable.bool_config?( name )
+    ent = get_entry(name) or return false
+    ent[1] == 'yes/no'
+  end
+
+  def ConfigTable.value_config?( name )
+    ent = get_entry(name) or return false
+    ent[1] != 'yes/no'
+  end
+
+  def ConfigTable.path_config?( name )
+    ent = get_entry(name) or return false
+    ent[1] == 'path'
+  end
+
+
+  class << self
+
+    alias newobj new
+
+    def new
+      c = newobj()
+      c.__send__ :init
+      c
+    end
+
+    def load
+      c = newobj()
+      File.file? SAVE_FILE or
+              raise InstallError, "#{File.basename $0} config first"
+      File.foreach( SAVE_FILE ) do |line|
+        k, v = line.split( '=', 2 )
+        c.instance_eval {
+            @table[k] = v.strip
+        }
+      end
+      c
+    end
+  
+  end
+
+  def initialize
+    @table = {}
+  end
+
+  def init
+    DESCRIPTER.each do |k, (default, vname, desc, default2)|
+      @table[k] = default
+    end
+  end
+  private :init
+
+  def save
+    File.open( SAVE_FILE, 'w' ) {|f|
+        @table.each do |k, v|
+          f.printf "%s=%s\n", k, v if v
+        end
+    }
+  end
+
+  def []=( k, v )
+    ConfigTable.config_key? k or raise InstallError, "unknown config option #{k}"
+    if ConfigTable.path_config? k then
+      @table[k] = (v[0,1] != '$') ? File.expand_path(v) : v
+    else
+      @table[k] = v
+    end
+  end
+    
+  def []( key )
+    @table[key] or return nil
+    @table[key].gsub( %r<\$([^/]+)> ) { self[$1] }
+  end
+
+  def set_raw( key, val )
+    @table[key] = val
+  end
+
+  def get_raw( key )
+    @table[key]
+  end
+
+end
+
+
+class MetaConfigEnvironment
+
+  def self.eval_file( file )
+    return unless File.file? file
+    new.instance_eval File.read_all(file), file, 1
+  end
+
+  private
+
+  def config_names
+    ConfigTable.keys
+  end
+
+  def config?( name )
+    ConfigTable.config_key? name
+  end
+
+  def bool_config?( name )
+    ConfigTable.bool_config? name
+  end
+
+  def value_config?( name )
+    ConfigTable.value_config? name
+  end
+
+  def path_config?( name )
+    ConfigTable.path_config? name
+  end
+
+  def add_config( name, argname, default, desc )
+    ConfigTable.add_entry name,[default,argname,desc]
+  end
+
+  def add_path_config( name, default, desc )
+    add_config name, 'path', default, desc
+  end
+
+  def add_bool_config( name, default, desc )
+    add_config name, 'yes/no', default ? 'yes' : 'no', desc
+  end
+
+  def set_config_default( name, default )
+    if bool_config? name then
+      ConfigTable.get_entry!(name)[0] = default ? 'yes' : 'no'
+    else
+      ConfigTable.get_entry!(name)[0] = default
+    end
+  end
+
+  def remove_config( name )
+    ent = ConfigTable.get_entry(name)
+    ConfigTable.remove_entry name
+    ent
+  end
+
+end
+
+### end config.rb
+### begin fileop.rb
+
+module FileOperations
+
+  def mkdir_p( dname, prefix = nil )
+    dname = prefix + dname if prefix
+    $stderr.puts "mkdir -p #{dname}" if verbose?
+    return if no_harm?
+
+    # does not check '/'... it's too abnormal case
+    dirs = dname.split(%r_(?=/)_)
+    if /\A[a-z]:\z/i === dirs[0] then
+      disk = dirs.shift
+      dirs[0] = disk + dirs[0]
+    end
+    dirs.each_index do |idx|
+      path = dirs[0..idx].join('')
+      Dir.mkdir path unless dir? path
+    end
+  end
+
+  def rm_f( fname )
+    $stderr.puts "rm -f #{fname}" if verbose?
+    return if no_harm?
+
+    if File.exist? fname or File.symlink? fname then
+      File.chmod 0777, fname
+      File.unlink fname
+    end
+  end
+
+  def rm_rf( dn )
+    $stderr.puts "rm -rf #{dn}" if verbose?
+    return if no_harm?
+
+    Dir.chdir dn
+    Dir.foreach('.') do |fn|
+      next if fn == '.'
+      next if fn == '..'
+      if dir? fn then
+        verbose_off {
+            rm_rf fn
+        }
+      else
+        verbose_off {
+            rm_f fn
+        }
+      end
+    end
+    Dir.chdir '..'
+    Dir.rmdir dn
+  end
+
+  def mv( src, dest )
+    rm_f dest
+    begin
+      File.link src, dest
+    rescue
+      File.write dest, File.read_all(src)
+      File.chmod File.stat(src).mode, dest
+    end
+    rm_f src
+  end
+
+  def install( from, dest, mode, prefix = nil )
+    $stderr.puts "install #{from} #{dest}" if verbose?
+    return if no_harm?
+
+    realdest = prefix + dest if prefix
+    if dir? realdest then
+      realdest += '/' + File.basename(from)
+    end
+    str = File.read_all(from)
+    if diff? str, realdest then
+      verbose_off {
+          rm_f realdest if File.exist? realdest
+      }
+      File.write realdest, str
+      File.chmod mode, realdest
+
+      File.open( objdir + '/InstalledFiles', 'a' ) {|f| f.puts realdest }
+    end
+  end
+
+  def diff?( orig, targ )
+    return true unless File.exist? targ
+    orig != File.read_all(targ)
+  end
+
+  def command( str )
+    $stderr.puts str if verbose?
+    system str or raise RuntimeError, "'system #{str}' failed"
+  end
+
+  def ruby( str )
+    command config('ruby-prog') + ' ' + str
+  end
+
+  def dir?( dname )
+    # for corrupted windows stat()
+    File.directory?( (dname[-1,1] == '/') ? dname : dname + '/' )
+  end
+
+  def all_files( dname )
+    Dir.open( dname ) {|d|
+        return d.find_all {|n| File.file? "#{dname}/#{n}" }
+    }
+  end
+
+  def all_dirs( dname )
+    Dir.open( dname ) {|d|
+        return d.find_all {|n| dir? "#{dname}/#{n}" } - %w(. ..)
+    }
+  end
+
+end
+
+### end fileop.rb
+### begin base.rb
+
+class InstallError < StandardError; end
+
+
+class Installer
+
+  Version   = '3.1.2'
+  Copyright = 'Copyright (c) 2000-2002 Minero Aoki'
+
+
+  @toplevel = nil
+
+  def self.declear_toplevel_installer( inst )
+    @toplevel and
+        raise ArgumentError, 'more than one toplevel installer decleared'
+    @toplevel = inst
+  end
+
+  def self.toplevel_installer
+    @toplevel
+  end
+
+
+  FILETYPES = %w( bin lib ext data )
+
+  include FileOperations
+
+  def initialize( config, opt, srcroot, objroot )
+    @config = config
+    @options = opt
+    @srcdir = File.expand_path(srcroot)
+    @objdir = File.expand_path(objroot)
+    @currdir = '.'
+  end
+
+  def inspect
+    "#<#{type} #{__id__}>"
+  end
+
+  #
+  # configs/options
+  #
+
+  def get_config( key )
+    @config[key]
+  end
+
+  alias config get_config
+
+  def set_config( key, val )
+    @config[key] = val
+  end
+
+  def no_harm?
+    @options['no-harm']
+  end
+
+  def verbose?
+    @options['verbose']
+  end
+
+  def verbose_off
+    save, @options['verbose'] = @options['verbose'], false
+    yield
+    @options['verbose'] = save
+  end
+
+  #
+  # srcdir/objdir
+  #
+
+  attr_reader :srcdir
+  alias srcdir_root srcdir
+  alias package_root srcdir
+
+  def curr_srcdir
+    "#{@srcdir}/#{@currdir}"
+  end
+
+  attr_reader :objdir
+  alias objdir_root objdir
+
+  def curr_objdir
+    "#{@objdir}/#{@currdir}"
+  end
+
+  def srcfile( path )
+    curr_srcdir + '/' + path
+  end
+
+  def srcexist?( path )
+    File.exist? srcfile(path)
+  end
+
+  def srcdirectory?( path )
+    dir? srcfile(path)
+  end
+  
+  def srcfile?( path )
+    File.file? srcfile(path)
+  end
+
+  def srcentries( path = '.' )
+    Dir.open( curr_srcdir + '/' + path ) {|d|
+        return d.to_a - %w(. ..) - hookfilenames
+    }
+  end
+
+  def srcfiles( path = '.' )
+    srcentries(path).find_all {|fname|
+        File.file? File.join(curr_srcdir, path, fname)
+    }
+  end
+
+  def srcdirectories( path = '.' )
+    srcentries(path).find_all {|fname|
+        dir? File.join(curr_srcdir, path, fname)
+    }
+  end
+
+  def dive_into( rel )
+    return unless dir? "#{@srcdir}/#{rel}"
+
+    dir = File.basename(rel)
+    Dir.mkdir dir unless dir? dir
+    save = Dir.pwd
+    Dir.chdir dir
+    $stderr.puts '---> ' + rel if verbose?
+    @currdir = rel
+    yield
+    Dir.chdir save
+    $stderr.puts '<--- ' + rel if verbose?
+    @currdir = File.dirname(rel)
+  end
+
+  #
+  # config
+  #
+
+  def exec_config
+    exec_task_traverse 'config'
+  end
+
+  def config_dir_bin( rel )
+  end
+
+  def config_dir_lib( rel )
+  end
+
+  def config_dir_ext( rel )
+    extconf if extdir? curr_srcdir
+  end
+
+  def extconf
+    opt = @options['config-opt'].join(' ')
+    command "#{config('ruby-prog')} #{curr_srcdir}/extconf.rb #{opt}"
+  end
+
+  def config_dir_data( rel )
+  end
+
+  #
+  # setup
+  #
+
+  def exec_setup
+    exec_task_traverse 'setup'
+  end
+
+  def setup_dir_bin( relpath )
+    all_files( curr_srcdir ).each do |fname|
+      add_rubypath "#{curr_srcdir}/#{fname}"
+    end
+  end
+
+  SHEBANG_RE = /\A\#!\s*\S*ruby\S*/
+
+  def add_rubypath( path )
+    $stderr.puts %Q<set #! line to "\#!#{config('ruby-path')}" for #{path} ...> if verbose?
+    return if no_harm?
+
+    tmpfile = File.basename(path) + '.tmp'
+    begin
+      File.open( path ) {|r|
+      File.open( tmpfile, 'w' ) {|w|
+          first = r.gets
+          return unless SHEBANG_RE === first   # reject '/usr/bin/env ruby'
+
+          w.print first.sub( SHEBANG_RE, '#!' + config('ruby-path') )
+          w.write r.read
+      } }
+      mv tmpfile, File.basename(path)
+    ensure
+      rm_f tmpfile if File.exist? tmpfile
+    end
+  end
+
+  def setup_dir_lib( relpath )
+  end
+
+  def setup_dir_ext( relpath )
+    if extdir? curr_srcdir then
+      make
+    end
+  end
+
+  def make
+    command config('make-prog')
+  end
+
+  def setup_dir_data( relpath )
+  end
+
+  #
+  # install
+  #
+
+  def exec_install
+    exec_task_traverse 'install'
+  end
+
+  def install_dir_bin( rel )
+    install_files targfiles, config('bin-dir') + '/' + rel, 0755
+  end
+
+  def install_dir_lib( rel )
+    install_files targfiles, config('rb-dir') + '/' + rel, 0644
+  end
+
+  def install_dir_ext( rel )
+    if extdir? curr_srcdir then
+      install_dir_ext_main File.dirname(rel)
+    end
+  end
+
+  def install_dir_ext_main( rel )
+    install_files allext('.'), config('so-dir') + '/' + rel, 0555
+  end
+
+  def install_dir_data( rel )
+    install_files targfiles, config('data-dir') + '/' + rel, 0644
+  end
+
+  def install_files( list, dest, mode )
+    mkdir_p dest, @options['install-prefix']
+    list.each do |fname|
+      install fname, dest, mode, @options['install-prefix']
+    end
+  end
+  
+  def targfiles
+    (targfilenames() - hookfilenames()).collect {|fname|
+        File.exist?(fname) ? fname : File.join(curr_srcdir(), fname)
+    }
+  end
+
+  def targfilenames
+    [ curr_srcdir(), '.' ].inject([]) {|ret, dir|
+        ret | all_files(dir)
+    }
+  end
+
+  def hookfilenames
+    %w( pre-%s post-%s pre-%s.rb post-%s.rb ).collect {|fmt|
+        %w( config setup install clean ).collect {|t| sprintf fmt, t }
+    }.flatten
+  end
+
+  def allext( dir )
+    _allext(dir) or raise InstallError,
+        "no extention exists: Have you done 'ruby #{$0} setup' ?"
+  end
+
+  DLEXT = /\.#{ ::Config::CONFIG['DLEXT'] }\z/
+
+  def _allext( dir )
+    Dir.open( dir ) {|d|
+        return d.find_all {|fname| DLEXT === fname }
+    }
+  end
+
+  #
+  # clean
+  #
+
+  def exec_clean
+    exec_task_traverse 'clean'
+    rm_f 'config.save'
+    rm_f 'InstalledFiles'
+  end
+
+  def clean_dir_bin( rel )
+  end
+
+  def clean_dir_lib( rel )
+  end
+
+  def clean_dir_ext( rel )
+    clean
+  end
+  
+  def clean
+    command config('make-prog') + ' clean' if File.file? 'Makefile'
+  end
+
+  def clean_dir_data( rel )
+  end
+
+  #
+  # lib
+  #
+
+  def exec_task_traverse( task )
+    run_hook 'pre-' + task
+    FILETYPES.each do |type|
+      if config('without-ext') == 'yes' and type == 'ext' then
+        $stderr.puts 'skipping ext/* by user option' if verbose?
+        next
+      end
+      traverse task, type, task + '_dir_' + type
+    end
+    run_hook 'post-' + task
+  end
+
+  def traverse( task, rel, mid )
+    dive_into( rel ) {
+        run_hook 'pre-' + task
+        __send__ mid, rel.sub( %r_\A.*?(?:/|\z)_, '' )
+        all_dirs( curr_srcdir ).each do |d|
+          traverse task, rel + '/' + d, mid
+        end
+        run_hook 'post-' + task
+    }
+  end
+
+  def run_hook( name )
+    try_run_hook curr_srcdir + '/' + name           or
+    try_run_hook curr_srcdir + '/' + name + '.rb'
+  end
+
+  def try_run_hook( fname )
+    return false unless File.file? fname
+
+    env = self.dup
+    begin
+      env.instance_eval File.read_all(fname), fname, 1
+    rescue
+      raise InstallError, "hook #{fname} failed:\n" + $!.message
+    end
+    true
+  end
+
+  def extdir?( dir )
+    File.exist? dir + '/MANIFEST'
+  end
+
+end
+
+### end base.rb
+### begin toplevel.rb
+
+class ToplevelInstaller < Installer
+
+  TASKS = [
+    [ 'config',   'saves your configurations' ],
+    [ 'show',     'shows current configuration' ],
+    [ 'setup',    'compiles extention or else' ],
+    [ 'install',  'installs files' ],
+    [ 'clean',    "does `make clean' for each extention" ]
+  ]
+
+
+  def initialize( root )
+    super nil, {'verbose' => true}, root, '.'
+    Installer.declear_toplevel_installer self
+  end
+
+
+  def execute
+    run_metaconfigs
+
+    case task = parsearg_global()
+    when 'config'
+      @config = ConfigTable.new
+    else
+      @config = ConfigTable.load
+    end
+    parsearg_TASK task
+
+    exectask task
+  end
+
+
+  def run_metaconfigs
+    MetaConfigEnvironment.eval_file "#{srcdir_root}/#{metaconfig}"
+  end
+
+  def metaconfig
+    'metaconfig'
+  end
+
+
+  def exectask( task )
+    if task == 'show' then
+      exec_show
+    else
+      try task
+    end
+  end
+
+  def try( task )
+    $stderr.printf "#{File.basename $0}: entering %s phase...\n", task if verbose?
+    begin
+      __send__ 'exec_' + task
+    rescue
+      $stderr.printf "%s failed\n", task
+      raise
+    end
+    $stderr.printf "#{File.basename $0}: %s done.\n", task if verbose?
+  end
+
+  #
+  # processing arguments
+  #
+
+  def parsearg_global
+    task_re = /\A(?:#{TASKS.collect {|i| i[0] }.join '|'})\z/
+
+    while arg = ARGV.shift do
+      case arg
+      when /\A\w+\z/
+        task_re === arg or raise InstallError, "wrong task: #{arg}"
+        return arg
+
+      when '-q', '--quiet'
+        @options['verbose'] = false
+
+      when       '--verbose'
+        @options['verbose'] = true
+
+      when '-h', '--help'
+        print_usage $stdout
+        exit 0
+
+      when '-v', '--version'
+        puts "#{File.basename $0} version #{Version}"
+        exit 0
+      
+      when '--copyright'
+        puts Copyright
+        exit 0
+
+      else
+        raise InstallError, "unknown global option '#{arg}'"
+      end
+    end
+
+    raise InstallError, 'no task or global option given'
+  end
+
+
+  def parsearg_TASK( task )
+    mid = "parsearg_#{task}"
+    if respond_to? mid, true then
+      __send__ mid
+    else
+      ARGV.empty? or
+          raise InstallError, "#{task}:  unknown options: #{ARGV.join ' '}"
+    end
+  end
+
+  def parsearg_config
+    re = /\A--(#{ConfigTable.keys.join '|'})(?:=(.*))?\z/
+    @options['config-opt'] = []
+
+    while i = ARGV.shift do
+      if /\A--?\z/ === i then
+        @options['config-opt'] = ARGV.dup
+        break
+      end
+      m = re.match(i) or raise InstallError, "config: unknown option #{i}"
+      name, value = m.to_a[1,2]
+      if value then
+        if ConfigTable.bool_config?(name) then
+          /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i === value or raise InstallError, "config: --#{name} allows only yes/no for argument"
+          value = (/\Ay(es)?|\At(rue)/i === value) ? 'yes' : 'no'
+        end
+      else
+        ConfigTable.bool_config?(name) or raise InstallError, "config: --#{name} requires argument"
+        value = 'yes'
+      end
+      @config[name] = value
+    end
+  end
+
+  def parsearg_install
+    @options['no-harm'] = false
+    @options['install-prefix'] = ''
+    while a = ARGV.shift do
+      case a
+      when /\A--no-harm\z/
+        @options['no-harm'] = true
+      when /\A--prefix=(.*)\z/
+        path = $1
+        path = File.expand_path(path) unless path[0,1] == '/'
+        @options['install-prefix'] = path
+      else
+        raise InstallError, "install: unknown option #{a}"
+      end
+    end
+  end
+
+
+  def print_usage( out )
+    out.puts
+    out.puts 'Usage:'
+    out.puts "  ruby #{File.basename $0} <global option>"
+    out.puts "  ruby #{File.basename $0} [<global options>] <task> [<task options>]"
+
+    fmt = "  %-20s %s\n"
+    out.puts
+    out.puts 'Global options:'
+    out.printf fmt, '-q,--quiet',   'suppress message outputs'
+    out.printf fmt, '   --verbose', 'output messages verbosely'
+    out.printf fmt, '-h,--help',    'print this message'
+    out.printf fmt, '-v,--version', 'print version and quit'
+    out.printf fmt, '--copyright',  'print copyright and quit'
+
+    out.puts
+    out.puts 'Tasks:'
+    TASKS.each do |name, desc|
+      out.printf "  %-10s  %s\n", name, desc
+    end
+
+    out.puts
+    out.puts 'Options for config:'
+    ConfigTable.each_definition do |name, (default, arg, desc, default2)|
+      out.printf "  %-20s %s [%s]\n",
+                 '--'+ name + (ConfigTable.bool_config?(name) ? '' : '='+arg),
+                 desc,
+                 default2 || default
+    end
+    out.printf "  %-20s %s [%s]\n",
+        '--rbconfig=path', 'your rbconfig.rb to load', "running ruby's"
+
+    out.puts
+    out.puts 'Options for install:'
+    out.printf "  %-20s %s [%s]\n",
+        '--no-harm', 'only display what to do if given', 'off'
+
+    out.puts
+  end
+
+  #
+  # config
+  #
+
+  def exec_config
+    super
+    @config.save
+  end
+
+  #
+  # show
+  #
+
+  def exec_show
+    ConfigTable.each_name do |k|
+      v = @config.get_raw(k)
+      if not v or v.empty? then
+        v = '(not specified)'
+      end
+      printf "%-10s %s\n", k, v
+    end
+  end
+
+end
+
+### end toplevel.rb
+
+if $0 == __FILE__ then
+  begin
+    installer = ToplevelInstaller.new( File.dirname($0) )
+    installer.execute
+  rescue
+    raise if $DEBUG
+    $stderr.puts $!.message
+    $stderr.puts "try 'ruby #{$0} --help' for usage"
+    exit 1
+  end
+end
Index: /tdiary/branches/upstream/contrib/lib/exifparser/README
===================================================================
--- /tdiary/branches/upstream/contrib/lib/exifparser/README (revision 710)
+++ /tdiary/branches/upstream/contrib/lib/exifparser/README (revision 710)
@@ -0,0 +1,15 @@
+Ruby Exif parser - Exif tag parser written in pure Ruby
+
+= Install
+
+$ ruby install.rb config
+$ ruby install.rb setup
+# ruby install.rb install
+
+= License
+
+Ruby's
+
+= Author
+
+Ryuichi Tamura (r-tam@fsinet.or.jp)
Index: /tdiary/branches/upstream/contrib/lib/rexif_gps.rb
===================================================================
--- /tdiary/branches/upstream/contrib/lib/rexif_gps.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/lib/rexif_gps.rb (revision 710)
@@ -0,0 +1,1355 @@
+#
+# rexif_gps.rb
+#  -- exif parser library (with GPS)
+#
+# kp <k-nomura@s6.dion.ne.jp>
+#
+# オリジナルからの変更点
+#  ・InterOperabilityIFD対応削除
+#  ・GPS IFDへの対応
+#  ・Rational.new→Rational.new!
+#
+
+require 'rational'
+require 'rjpeg'
+
+class Exif<Jpeg::Segment
+	class ParseError<Jpeg::ParseError
+	end
+
+	#
+	# Data with read class
+	#
+	class Data
+		PACKSTR={
+			:le=>{
+				:ushort=>'v*',
+				:ulong=>'V*',
+				:float=>'e*',
+				:double=>'E*'
+			}.freeze,
+			:be=>{
+				:ushort=>'n*',
+				:ulong=>'N*',
+				:float=>'g*',
+				:double=>'G*'
+			}.freeze
+		}
+		PACKSTR.freeze
+
+		def initialize(data,endian=:le)
+			@data=data
+			@endian=endian
+		end
+		attr_reader :endian
+
+		def get_ushort(offset=0)
+			self.read_ushort(offset,1).first
+		end
+		def get_ulong(offset=0)
+			self.read_ulong(offset,1).first
+		end
+
+		def read_char(offset=0,n=1)
+			@data[offset..offset+n-1]
+		end
+		def read_ascii(offset=0,n=1)
+			@data[offset..offset+n-1].unpack('A*')
+		end
+		def read_byte(offset=0,n=1)
+			@data[offset..offset+n-1].unpack('c*')
+		end
+		def read_ubyte(offset=0,n=1)
+			@data[offset..offset+n-1].unpack('C*')
+		end
+		def read_short(offset=0,n=1)
+			self.read_ushort(offset,n).pack('s*').unpack('s*')
+		end
+		def read_long(offset=0,n=1)
+			self.read_ulong(offset,n).pack('l*').unpack('l*')
+		end
+		def read_rational(offset=0,n=1)
+			ret=[]
+			buf=self.read_long(offset,n*2)
+			until(buf.empty?)
+				ret.push(Exif::Rational.new!(buf.shift,buf.shift))
+			end
+			ret
+		end
+		def read_urational(offset=0,n=1)
+			ret=[]
+			buf=self.read_ulong(offset,n*2)
+			until(buf.empty?)
+				ret.push(Exif::Rational.new!(buf.shift,buf.shift))
+			end
+			ret
+		end
+
+		def read_ushort(offset=0,n=1)
+			@data[offset..offset+n*2-1].unpack(PACKSTR[@endian][:ushort])
+		end
+		def read_ulong(offset=0,n=1)
+			@data[offset..offset+n*4-1].unpack(PACKSTR[@endian][:ulong])
+		end
+		def read_float(offset=0,n=1)
+			@data[offset..offset+n*4-1].unpack(PACKSTR[@endian][:float])
+		end
+		def read_double(offset=0,n=1)
+			@data[offset..offset+n*8-1].unpack(PACKSTR[@endian][:double])
+		end
+
+		def to_s
+			@data
+		end
+		alias dump to_s
+
+		def size
+			@data.size
+		end
+
+		def Data.new_char(x,endian=:le)
+			Data.new(x.to_s,endian)
+		end
+		def Data.new_ascii(x,endian=:le)
+			Data.new(x.to_a.pack('A*')+"\x00",endian)
+		end
+		def Data.new_byte(x,endian=:le)
+			Data.new(x.to_a.pack('c*'),endian)
+		end
+		def Data.new_ubyte(x,endian=:le)
+			Data.new(x.to_a.pack('C*'),endian)
+		end
+		def Data.new_short(x,endian=:le)
+			Data.new(x.to_a.pack('s*').
+			            unpack(PACKSTR[endian][:ushort]).
+			            pack(PACKSTR[endian][:ushort]),
+			         endian)
+		end
+		def Data.new_ushort(x,endian=:le)
+			Data.new(x.to_a.pack(PACKSTR[endian][:ushort]),
+			         endian)
+		end
+		def Data.new_long(x,endian=:le)
+			Data.new(x.to_a.pack('l*').
+			            unpack(PACKSTR[endian][:ulong]).
+			            pack(PACKSTR[endian][:ulong]),
+			         endian)
+		end
+		def Data.new_ulong(x,endian=:le)
+			Data.new(x.to_a.pack(PACKSTR[endian][:ulong]),endian)
+		end
+		def Data.new_rational(x,endian=:le)
+			Data.new_long(x.to_a.map{|a| a.to_a}.flatten,
+			              endian)
+		end
+		def Data.new_urational(x,endian=:le)
+			Data.new_ulong(x.to_a.map{|a| a.to_a}.flatten,
+			               endian)
+		end
+		def Data.new_float(x,endian=:le)
+			Data.new(x.to_a.pack(PACKSTR[endian][:float]),endian)
+		end
+		def Data.new_double(x,endian=:le)
+			Data.new(x.to_a.pack(PACKSTR[endian][:double]),endian)
+		end
+	end
+
+	class Rational<Rational
+		def to_a
+			[@numerator,@denominator]
+		end
+	end
+
+	#
+	# Exif::Ifd
+	#
+	class Ifd<Array
+
+		#
+		# Exif::Ifd::Directory
+		#
+		class Directory
+			#
+			# Tag Names
+			#
+			#    InteroperabilityIndex=0x0001
+			#    InteroperabilityVersion=0x0002
+
+# gps tags
+# added by kp
+
+			VersionID=0x0000
+			LatitudeRef=0x0001
+			Latitude=0x0002
+			LongitudeRef=0x0003
+			Longitude=0x0004
+			AltitudeRef=0x0005
+			Altitude=0x0006
+			TimeStamp=0x0007
+			Satellites=0x0008
+			Status=0x0009
+			MeasureMode=0x000a
+			Dop=0x000b
+			SpeedRef=0x000c
+			Speed=0x000d
+			TrackRef=0x000e
+			Track=0x000f
+			ImgDirectionRef=0x0010
+			ImgDirection=0x0011
+			MapDatum=0x0012
+			DestLatitudeRef=0x0013
+			DestLatitude=0x0014
+			DestLongitudeRef=0x0015
+			DestLongitude=0x0016
+			DestBearingRef=0x0017
+			DestBearing=0x0018
+			DestDistanceRef=0x0019
+			DestDistance=0x001a
+			ProcessingMethod=0x001b
+			AreaInformation=0x001c
+			DataStamp=0x001d
+			Differential=0x001e
+
+			NewSubfileType=0x00fe
+			SubfileType=0x00ff
+			ImageWidth=0x0100
+			ImageLength=0x0101
+			BitsPerSample=0x0102
+			Compression=0x0103
+			PhotometricInterpretation=0x0106
+			ImageDescription=0x010e
+			Maker=0x010f
+			Model=0x0110
+			StripOffsets=0x0111
+			Orientation=0x0112
+			SamplesPerPixel=0x0115
+			RowsPerStrip=0x0116
+			StripByteConunts=0x0117
+			XResolution=0x011a
+			YResolution=0x011b
+			PlanarConfiguration=0x011c
+			ResolutionUnit=0x0128
+			TransferFunction=0x012d
+			Software=0x0131
+			DateTime=0x0132
+			Artist=0x013b
+			Predictor=0x013d
+			WhitePoint=0x013e
+			PrimaryChromaticities=0x013f
+			TileWidth=0x0142
+			TileLength=0x0143
+			TileOffsets=0x0144
+			TileByteCounts=0x0145
+			SubIFDs=0x014a
+			JPEGTables=0x015b
+			JpegInterchangeFormat=0x0201
+			JpegInterchangeFormatLength=0x0202
+			YCbCrCoefficients=0x0211
+			YCbCrSubSampling=0x0212
+			YCbCrPositioning=0x0213
+			ReferenceBlackWhite=0x0214
+			RelatedImageFileFormat=0x1000
+			RelatedImageWidth=0x1001
+			CFARepeatPatternDim=0x828d
+			CFAPattern=0x828e
+			BatteryLevel=0x828f
+			Copyright=0x8298
+			ExposureTime=0x829a
+			FNumber=0x829d
+			IPTC_NAA=0x83bb
+			ExifIFDPointer=0x8769
+			InterColorProfile=0x8773
+			ExposureProgram=0x8822
+			SpectralSensitivity=0x8824
+			GPSIFDPointer=0x8825 #GPSInfo -> GPSIFDPointer
+			ISOSpeedRatings=0x8827
+			OECF=0x8828
+			Interlace=0x8829
+			TimeZoneOffset=0x882a
+			SelfTimerMode=0x882b
+			ExifVersion=0x9000
+			DateTimeOriginal=0x9003
+			DateTimeDigitized=0x9004
+			ComponentsConfiguration=0x9101
+			CompressedBitsPerPixel=0x9102
+			ShutterSpeedValue=0x9201
+			ApertureValue=0x9202
+			BrightnessValue=0x9203
+			ExposureBiasValue=0x9204
+			MaxApertureValue=0x9205
+			SubjectDistance=0x9206
+			MeteringMode=0x9207
+			LightSource=0x9208
+			Flash=0x9209
+			FocalLength=0x920a
+			FlashEnergy=0x920b
+			SpatialFrequencyResponse=0x920c
+			Noise=0x920d
+			ImageNumber=0x9211
+			SecurityClassification=0x9212
+			ImageHistory=0x9213
+			SubjectLocation=0x9214
+			ExposureIndex=0x9215
+			TIFF_EPStandardID=0x9216
+			MakerNote=0x927c
+			UserComment=0x9286
+			SubSecTime=0x9290
+			SubSecTimeOriginal=0x9291
+			SubSecTimeDigitized=0x9292
+			FlashPixVersion=0xa000
+			ColorSpace=0xa001
+			ExifImageWidth=0xa002
+			ExifImageHeight=0xa003
+			RelatedSoundFile=0xa004
+			InteroperabilityIFDPointer=0xa005
+			FlashEnergy2=0xa20b
+			SpatialFrequencyResponse2=0xa20c
+			FocalPlaneXResolution=0xa20e
+			FocalPlaneYResolution=0xa20f
+			FocalPlaneResolutionUnit=0xa210
+			SubjectLocation2=0xa214
+			ExposureIndex2=0xa215
+			SensingMethod=0xa217
+			FileSource=0xa300
+			SceneType=0xa301
+			CFAPattern2=0xa302
+
+			class TagInfo
+				def initialize(name,format,limit=0)
+					@name=name
+					@format=format.to_a.freeze
+					@limit=limit
+				end
+				attr_reader :name,:format,:limit
+			end
+
+			TAG_NAME={
+#				0x0001=>TagInfo.new("InteroperabilityIndex",[2],0),
+#				0x0002=>TagInfo.new("InteroperabilityVersion",[7],4),
+
+				0x0000=>TagInfo.new("VersionID",[1],4),
+				0x0001=>TagInfo.new("LatitudeRef",[2],2),
+				0x0002=>TagInfo.new("Latitude",[5],3),
+				0x0003=>TagInfo.new("LongitudeRef",[2],2),
+				0x0004=>TagInfo.new("Longitude",[5],3),
+				0x0005=>TagInfo.new("AltitudeRef",[1],1),
+				0x0006=>TagInfo.new("Altitude",[5],1),
+				0x0007=>TagInfo.new("TimeStamp",[5],3),
+				0x0008=>TagInfo.new("Satellites",[1],0),
+				0x0009=>TagInfo.new("Status",[2],2),
+				0x000a=>TagInfo.new("MeasureMode",[2],2),
+				0x000b=>TagInfo.new("Dop",[5],1),
+				0x000c=>TagInfo.new("SpeedRef",[2],2),
+				0x000d=>TagInfo.new("Speed",[5],1),
+				0x000e=>TagInfo.new("TrackRef",[2],2),
+				0x000f=>TagInfo.new("Track",[5],1),
+				0x0010=>TagInfo.new("ImgDirectionRef",[2],2),
+				0x0011=>TagInfo.new("ImgDirection",[5],1),
+				0x0012=>TagInfo.new("MapDatum",[2],0),
+				0x0013=>TagInfo.new("DestLatitudeRef",[2],2),
+				0x0014=>TagInfo.new("DestLatitude",[5],3),
+				0x0015=>TagInfo.new("DestLongitudeRef",[2],2),
+				0x0016=>TagInfo.new("DestLongitude",[5],3),
+				0x0017=>TagInfo.new("DestBearingRef",[2],2),
+				0x0018=>TagInfo.new("DestBearing",[5],1),
+				0x0019=>TagInfo.new("DestDistanceRef",[2],2),
+				0x001a=>TagInfo.new("DestDistance",[5],1),
+				0x001b=>TagInfo.new("ProcessingMethod",[7],0),
+				0x001c=>TagInfo.new("AreaInformation",[7],0),
+				0x001d=>TagInfo.new("DataStamp",[2],11),
+				0x001e=>TagInfo.new("Differential",[3],1),
+
+				0x00fe=>TagInfo.new("NewSubfileType",[4],1),
+				0x00ff=>TagInfo.new("SubfileType",[3],1),
+				0x0100=>TagInfo.new("ImageWidth",[3,9],1),
+				0x0101=>TagInfo.new("ImageLength",[3,9],1),
+				0x0102=>TagInfo.new("BitsPerSample",[3],3),
+				0x0103=>TagInfo.new("Compression",[3],1),
+				0x0106=>TagInfo.new("PhotometricInterpretation",[3],1),
+				0x010e=>TagInfo.new("ImageDescription",[2],0),
+				0x010f=>TagInfo.new("Maker",[2],0),
+				0x0110=>TagInfo.new("Model",[2],0),
+				0x0111=>TagInfo.new("StripOffsets",[3,9],0),
+				0x0112=>TagInfo.new("Orientation",[3],1),
+				0x0115=>TagInfo.new("SamplesPerPixel",[3],1),
+				0x0116=>TagInfo.new("RowsPerStrip",[3,9],1),
+				0x0117=>TagInfo.new("StripByteConunts",[3,9],0),
+				0x011a=>TagInfo.new("XResolution",[5],1),
+				0x011b=>TagInfo.new("YResolution",[5],1),
+				0x011c=>TagInfo.new("PlanarConfiguration",[3],1),
+				0x0128=>TagInfo.new("ResolutionUnit",[3],1),
+				0x012d=>TagInfo.new("TransferFunction",[3],3),
+				0x0131=>TagInfo.new("Software",[2],0),
+				0x0132=>TagInfo.new("DateTime",[2],20),
+				0x013b=>TagInfo.new("Artist",[2],0),
+				0x013d=>TagInfo.new("Predictor",[3],1),
+				0x013e=>TagInfo.new("WhitePoint",[5],2),
+				0x013f=>TagInfo.new("PrimaryChromaticities",[5],6),
+				0x0142=>TagInfo.new("TileWidth",[3],1),
+				0x0143=>TagInfo.new("TileLength",[3],1),
+				0x0144=>TagInfo.new("TileOffsets",[4],0),
+				0x0145=>TagInfo.new("TileByteCounts",[3],0),
+				0x014a=>TagInfo.new("SubIFDs",[4],0),
+				0x015b=>TagInfo.new("JPEGTables",[7],0),
+				0x0201=>TagInfo.new("JpegInterchangeFormat",[4],1),
+				0x0202=>TagInfo.new("JpegInterchangeFormatLength",[4],1),
+				0x0211=>TagInfo.new("YCbCrCoefficients",[5],3),
+				0x0212=>TagInfo.new("YCbCrSubSampling",[3],2),
+				0x0213=>TagInfo.new("YCbCrPositioning",[3],1),
+				0x0214=>TagInfo.new("ReferenceBlackWhite",[5],6),
+				0x1000=>TagInfo.new("RelatedImageFileFormat",[2],0),
+				0x1001=>TagInfo.new("RelatedImageLength",[8],0),
+				0x1001=>TagInfo.new("RelatedImageWidth",[8],0),
+				0x828d=>TagInfo.new("CFARepeatPatternDim",[3],2),
+				0x828e=>TagInfo.new("CFAPattern",[1],0),
+				0x828f=>TagInfo.new("BatteryLevel",[5],1),
+				0x8298=>TagInfo.new("Copyright",[2],0),
+				0x829a=>TagInfo.new("ExposureTime",[5],1),
+				0x829d=>TagInfo.new("FNumber",[5],1),
+				0x83bb=>TagInfo.new("IPTC_NAA",[4],0),
+				0x8769=>TagInfo.new("ExifIFDPointer",[4],1),
+				0x8773=>TagInfo.new("InterColorProfile",[7],0),
+				0x8822=>TagInfo.new("ExposureProgram",[3],1),
+				0x8824=>TagInfo.new("SpectralSensitivity",[2],0),
+				0x8825=>TagInfo.new("GPSIFDPointer",[4],1),
+				0x8827=>TagInfo.new("ISOSpeedRatings",[3],2),
+				0x8828=>TagInfo.new("OECF",[7],0),
+				0x8829=>TagInfo.new("Interlace",[3],1),
+				0x882a=>TagInfo.new("TimeZoneOffset",[8],1),
+				0x882b=>TagInfo.new("SelfTimerMode",[3],1),
+				0x9000=>TagInfo.new("ExifVersion",[7],4),
+				0x9003=>TagInfo.new("DateTimeOriginal",[2],20),
+				0x9004=>TagInfo.new("DateTimeDigitized",[2],20),
+				0x9101=>TagInfo.new("ComponentsConfiguration",[7],4),
+				0x9102=>TagInfo.new("CompressedBitsPerPixel",[5],1),
+				0x9201=>TagInfo.new("ShutterSpeedValue",[10],1),
+				0x9202=>TagInfo.new("ApertureValue",[5],1),
+				0x9203=>TagInfo.new("BrightnessValue",[10],1),
+				0x9204=>TagInfo.new("ExposureBiasValue",[10],1),
+				0x9205=>TagInfo.new("MaxApertureValue",[5],1),
+				0x9206=>TagInfo.new("SubjectDistance",[10],1),
+				0x9207=>TagInfo.new("MeteringMode",[3],1),
+				0x9208=>TagInfo.new("LightSource",[3],1),
+				0x9209=>TagInfo.new("Flash",[3],1),
+				0x920a=>TagInfo.new("FocalLength",[5],1),
+				0x920b=>TagInfo.new("FlashEnergy",[5],1),
+				0x920c=>TagInfo.new("SpatialFrequencyResponse",[7],0),
+				0x920d=>TagInfo.new("Noise",[7],0),
+				0x9211=>TagInfo.new("ImageNumber",[4],1),
+				0x9212=>TagInfo.new("SecurityClassification",[2],1),
+				0x9213=>TagInfo.new("ImageHistory",[2],0),
+				0x9214=>TagInfo.new("SubjectLocation",[3],4),
+				0x9215=>TagInfo.new("ExposureIndex",[5],1),
+				0x9216=>TagInfo.new("TIFF_EPStandardID",[1],4),
+				0x927c=>TagInfo.new("MakerNote",[7],0),
+				0x9286=>TagInfo.new("UserComment",[7],0),
+				0x9290=>TagInfo.new("SubSecTime",[2],0),
+				0x9290=>TagInfo.new("SubsecTime",[2],0),
+				0x9291=>TagInfo.new("SubSecTimeOriginal",[2],0),
+				0x9291=>TagInfo.new("SubsecTimeOriginal",[2],0),
+				0x9292=>TagInfo.new("SubSecTimeDigitized",[2],0),
+				0x9292=>TagInfo.new("SubsecTimeDigitized",[2],0),
+				0xa000=>TagInfo.new("FlashPixVersion",[7],4),
+				0xa001=>TagInfo.new("ColorSpace",[3],1),
+				0xa002=>TagInfo.new("ExifImageWidth",[3,9],1),
+				0xa003=>TagInfo.new("ExifImageHeight",[3,9],1),
+				0xa004=>TagInfo.new("RelatedSoundFile",[2],0),
+				0xa005=>TagInfo.new("InteroperabilityIFDPointer",[4],1),
+				0xa20b=>TagInfo.new("FlashEnergy",[5],1),
+				0xa20c=>TagInfo.new("SpatialFrequencyResponse",[3],1),
+				0xa20e=>TagInfo.new("FocalPlaneXResolution",[5],1),
+				0xa20f=>TagInfo.new("FocalPlaneYResolution",[5],1),
+				0xa210=>TagInfo.new("FocalPlaneResolutionUnit",[3],1),
+				0xa214=>TagInfo.new("SubjectLocation",[3],1),
+				0xa215=>TagInfo.new("ExposureIndex",[5],1),
+				0xa217=>TagInfo.new("SensingMethod",[3],1),
+				0xa300=>TagInfo.new("FileSource",[7],1),
+				0xa301=>TagInfo.new("SceneType",[7],1),
+				0xa302=>TagInfo.new("CFAPattern",[7],0)
+			}
+			TAG_NAME.freeze
+
+			DIRENT_SIZE=12  # tag(2bytes)+format(2bytes)+
+			                #    data_num(4bytes)+data(4bytes)
+
+			FORMAT_NAME=[nil,
+				'ubyte',
+				'ascii',
+				'ushort',
+				'ulong',
+				'urational',
+				'byte',
+				'undefined',
+				'short',
+				'long',
+				'rational',
+				'float',
+				'double']
+			FORMAT_NAME.freeze
+
+			FORMAT_SIZE=[nil,
+				1,1,2,4,8,1,
+				1,2,4,8,4,8]
+			FORMAT_SIZE.freeze
+
+			READ_PROC=[nil,
+				proc{|x,o,n| x.read_ubyte(o,n)},
+				proc{|x,o,n| x.read_ascii(o,n)},
+				proc{|x,o,n| x.read_ushort(o,n)},
+				proc{|x,o,n| x.read_ulong(o,n)},
+				proc{|x,o,n| x.read_urational(o,n)},
+				proc{|x,o,n| x.read_byte(o,n)},
+				proc{|x,o,n| x.read_char(o,n)},
+				proc{|x,o,n| x.read_short(o,n)},
+				proc{|x,o,n| x.read_long(o,n)},
+				proc{|x,o,n| x.read_rational(o,n)},
+				proc{|x,o,n| x.read_float(o,n)},
+				proc{|x,o,n| x.read_double(o,n)}
+			]
+			READ_PROC.freeze
+
+			PACK_PROC=[nil,
+				proc{|x,e| Data.new_ubyte(x,e).to_s},
+				proc{|x,e| Data.new_ascii(x,e).to_s},
+				proc{|x,e| Data.new_ushort(x,e).to_s},
+				proc{|x,e| Data.new_ulong(x,e).to_s},
+				proc{|x,e| Data.new_urational(x,e).to_s},
+				proc{|x,e| Data.new_byte(x,e).to_s},
+				proc{|x,e| Data.new_char(x,e).to_s},
+				proc{|x,e| Data.new_short(x,e).to_s},
+				proc{|x,e| Data.new_long(x,e).to_s},
+				proc{|x,e| Data.new_rational(x,e).to_s},
+				proc{|x,e| Data.new_float(x,e).to_s},
+				proc{|x,e| Data.new_double(x,e).to_s}
+			]
+			PACK_PROC.freeze
+
+			TIME_PARSE_PROC=proc{|x|
+				if(x=~/(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)/)
+					Time.mktime($1.to_i,$2.to_i,$3.to_i,
+					            $4.to_i,$5.to_i,$6.to_i)
+				else
+					x
+				end
+			}
+
+			@@value_parse_proc={}
+
+			#
+			# Exif::Ifd::Directory
+			#
+			def initialize(ifd_name='',x=nil,offset=0)
+				@tag=nil    # 2bytes
+				@format=nil # data format
+				#data_num   # Number of data
+				@value=[]   # data
+
+				@is_ifd=false
+
+				@ifd_name=ifd_name
+				@endian=:le
+
+				unless(x.nil?)
+					parse(x,offset)
+				end
+			end
+			attr_reader :tag,:format
+
+			def tag_name
+				n=TAG_NAME[@tag]
+				if(n.nil?)
+					"#{@ifd_name}.#{sprintf('0x%04x',@tag)}"
+				else
+					"#{@ifd_name}.#{n.name}"
+				end
+			end
+			def format_name
+				FORMAT_NAME[@format]
+			end
+
+			def parse(x,offset=0)
+				begin
+					@tag=x.get_ushort(offset)
+					offset+=2
+					@format=x.get_ushort(offset)
+					offset+=2
+					data_num=x.get_ulong(offset)
+					offset+=4
+				rescue NameError, TypeError
+					raise Exif::ParseError,
+					         "Invalid offset (#{sprintf("0x%04x",offset)}) "+
+					         "given for #{self.tag_name}."
+				end
+
+				begin
+					if(data_num*FORMAT_SIZE[@format]>4)
+						offset=x.get_ulong(offset)
+					end
+				rescue NameError, TypeError
+					raise Exif::ParseError,
+					         "Invalid format (#{@format}) "+
+					         "found at #{self.tag_name} "+
+					         "(offset: #{sprintf("0x%04x",offset)})"
+				end
+
+				@value=READ_PROC[@format].call(x,offset,data_num)
+
+				case @format
+				when 2
+					begin
+						@value=@value.first
+					rescue NameError
+					end
+				when 7
+					@value=Data.new(@value,x.endian)
+				else
+					begin
+						if(TAG_NAME[@tag].limit==1)
+							@value=@value.first
+						end
+					rescue NameError
+					end
+				end
+			end
+
+			def value
+				proc=@@value_parse_proc[@tag]
+				if(@@value_parse_proc.has_key?(@tag))
+					@@value_parse_proc[@tag].call(@value)
+				else
+					@value
+				end
+			end
+
+			def value=(x)
+				_size_check(x)
+				@format=_format_check(x)
+				if(x.kind_of?(Ifd))
+					@is_ifd=true
+				else
+					@is_ifd=false
+				end
+				@value=x
+			end
+
+			def is_ifd?
+				@is_ifd
+			end
+
+			def is_array?
+				(!@is_ifd)&&@value.kind_of?(Array)
+			end
+
+			def to_a
+				@value.to_a
+			end
+
+			def to_i
+				begin
+					@value.to_i
+				rescue NameError
+					0
+				end
+			end
+
+			def to_f
+				begin
+					@value.to_f
+				rescue NameError
+					0.0
+				end
+			end
+
+			def to_s
+				@value.to_s
+			end
+
+			def to_time
+				t=TIME_PARSE_PROC.call(@value)
+				if(t.kind_of?(Time))
+					t
+				else
+					Time.at(0)
+				end
+			end
+
+			def data_num
+				if(@value.kind_of?(Array)&&!@value.kind_of?(Ifd))
+					@value.size
+				elsif(@format==2)
+					@value.size+1
+				elsif(@format==7)
+					@value.size
+				else
+					1
+				end
+			end
+
+			def data_size
+				if(@is_ifd)
+					@value.byte_size
+				else
+					self.data_num*FORMAT_SIZE[@format]
+				end
+			end
+
+			def byte_size
+				s=self.data_size
+				if(s<=4)
+					DIRENT_SIZE
+				else
+					DIRENT_SIZE+s
+				end
+			end
+
+			def dump_head(offset=0,endian=:le)
+				if(self.data_size>4)
+					Data.new_ushort(@tag,endian).to_s+
+					Data.new_ushort(@format,endian).to_s+
+					Data.new_ulong(self.data_num,endian).to_s+
+					Data.new_ulong(offset,endian).to_s
+				else
+					data=self.dump_data(offset,endian,true)
+					Data.new_ushort(@tag,endian).to_s+
+					Data.new_ushort(@format,endian).to_s+
+					Data.new_ulong(self.data_num,endian).to_s+
+					data+"\x00"*(4-data.size)
+				end
+			end
+
+			def dump_data(offset=0,endian=:le,force_dump=false)
+				if(force_dump||self.data_size>4)
+					data=@value
+					if(@is_ifd)
+						data=data.dump(offset,endian,false)
+					else
+						if(data.kind_of?(Data))
+							data=data.to_s
+						end
+						data=PACK_PROC[@format].call(data,endian)
+					end
+				else
+					nil
+				end
+			end
+
+			def dump(offset=0,endian=:le)
+				[self.dump_head(offset,endian),
+				 self.dump_data(offset,endian)]
+			end
+
+			def Directory.use_proc_for(tag,proc)
+				@@value_parse_proc[tag]=proc
+			end
+
+			private
+			def _size_check(x)
+				s=1
+				if((x.kind_of?(Array)&&!x.kind_of?(Ifd))||
+					x.kind_of?(String))
+					s=x.size
+				end
+				if(TAG_NAME.has_key?(@tag))
+					if(TAG_NAME[@tag].limit>0&&TAG_NAME[@tag].limit<s)
+						raise TypeError,
+						         "#{self.tag_name} value size is limited to #{TAG_NAME[@tag].limit}."
+					end
+				end
+			end
+
+			def _format_check(x)
+				format=_guess_format_type(x)
+				if(format.member?(@format))
+					return @format
+				end
+				if(TAG_NAME.has_key?(@tag))
+					format.each{|f|
+						if(TAG_NAME[@tag].format.member?(f))
+							return f
+						end
+					}
+					f=TAG_NAME[@tag].format.map{|x|
+						FORMAT_NAME[x]
+					}.join('|')
+					raise TypeError,
+			"#{self.tag_name} requires #{f} value."
+				else
+					format.first
+				end
+			end
+
+			def _guess_format_type(x)
+				if(x.kind_of?(Ifd))
+					[7,4]
+				else
+					if(x.kind_of?(Array))
+						x=x.first
+					end
+					if(x.kind_of?(String))
+						[2,1,3,4,5,6,8,9,10,11,12,7]
+					elsif(x.kind_of?(Integer))
+						if(x<0)
+							if(x<-32768)
+								[9,7]
+							elsif(x<-128)
+								[8,9,7]
+							else
+								[6,8,9,7]
+							end
+						else
+							if(x>32767)
+								[4,9,7]
+							elsif(x>127)
+								[3,4,7,8,9]
+							else
+								[1,3,4,6,7,8,9]
+							end
+						end
+					elsif(x.kind_of?(Rational))
+						if(x.numerator>32767||x.denominator>32767)
+							[5,7]
+						else
+							[10,5,7]
+						end
+					elsif(x.kind_of?(Float))
+						[12,11,7]
+					else
+						[7]
+					end
+				end
+			end
+
+		end
+
+		IFD0='IFD0'
+		ImageIFD='IFD0'
+		IFD1='IFD1'
+		ThumnailIFD='IFD1'
+		ExifIFD='IFD0.ExifIFD'
+#		InteroperabilityIFD='IFD0.ExifIFD.InteroperabilityIFD'
+		GPSIFD='IFD0.ExifIFD.GPSIFD'
+
+		#
+		# Exif::Ifd < Array
+		#
+		def initialize(ifd_name='',x=nil,offset=0,&block)
+			super()
+
+			# dirnum=0   # Number of Directory Entry (2bytes)
+			# dirs=[]    # Directory Entries (12 * dirnum bytes)
+			# next_ifd=0 # Offset to next ifd (4bytes)
+
+			@ifd_name=ifd_name
+			@thumbnail=nil
+			unless(x.nil?)
+				parse(x,offset,&block)
+			end
+		end
+		attr_reader :ifd_name
+
+		def parse(x,offset=0,&block)
+			begin
+				n=x.get_ushort(offset)
+			rescue NameError,TypeError
+				raise Exif::ParseError,
+				         "Invalid offset (#{sprintf("0x%04x",offset)}) "+
+				         "given for #{@ifd_name}."
+			end
+			offset+=2
+
+			n.times{|i|
+				d=Directory.new(@ifd_name,x,offset)
+
+				if(block.nil?)
+					self.push(d)
+				else
+					self.push(yield(d))
+				end
+				offset+=Directory::DIRENT_SIZE
+
+				#
+				# create method to access directory
+				#
+				n=sprintf('x%04x',d.tag)
+				self.instance_eval("def #{n};self[#{self.size-1}];end")
+				n=Directory::TAG_NAME[d.tag]
+				unless(n.nil?)
+					n=n.name.gsub(/([a-z])(?=[A-Z])/,'\1_').downcase
+					self.instance_eval("alias #{n} #{sprintf('x%04x',d.tag)}")
+				end
+
+				if(d.tag==Directory::ExifIFDPointer||
+					d.tag==Directory::GPSIFDPointer)
+
+					n=d.tag_name.sub('Pointer','')
+					d.value=Ifd.new(n,x,d.value,&block)
+
+					#
+					# create method to access ifd
+					#
+					n=n.split('.')[-1].gsub(/([a-z])(?=[A-Z])/,'\1_').downcase
+					self.instance_eval("def #{n};self[#{self.size-1}].value;end")
+				end
+			}
+
+			self.flatten!
+			begin
+				offset=x.get_ulong(offset)
+			rescue NameError,TypeError
+				raise Exif::ParseError,
+				         "Invalid offset (#{sprintf("0x%04x",offset)}) "+
+				         "given for next ifd of #{@ifd_name}."
+			end
+
+			# get thumbnail
+			if(@ifd_name=='IFD1')
+				if(self.respond_to?('jpeg_interchange_format')&&
+					self.respond_to?('jpeg_interchange_format_length'))
+					#
+					# JPEG thumbnail
+					#
+					thumbnail_offset=self.jpeg_interchange_format.to_i
+					thumbnail_size=self.jpeg_interchange_format_length.to_i
+					unless(thumbnail_offset.nil?||thumbnail_size.nil?)
+						begin
+							@thumbnail=x.read_char(thumbnail_offset,thumbnail_size)
+
+							self.instance_eval('def thumbnail_image;@thumbnail;end')
+							self.instance_eval('def thumbnail_type;:jpeg;end')
+						rescue NameError,TypeError
+						end
+					end
+				elsif(self.respond_to?('strip_offsets')&&
+					   self.respond_to?('strip_byte_conunts'))
+					#
+					# TIFF thumbnail
+					#
+					thumbnail_offset=self.strip_offsets
+					thumbnail_size=self.strip_byte_conunts
+					unless(thumbnail_offset.nil?||thumbnail_size.nil?)
+						@thumbnail=[]
+						o=thumbnail_offset.shift
+						s=thumbnail_size.shift
+						begin
+							until(o.nil?||s.nil?)
+								@thumbnail.push(x.read_char(o,s))
+								o=thumbnail_offset.shift
+								s=thumbnail_size.shift
+							end
+							self.instance_eval('def thumbnail_image;@thumbnail;end')
+							self.instance_eval('def thumbnail_type;:tiff;end')
+						rescue NameError,TypeError
+						end
+					end
+				end
+			end
+
+			offset
+		end
+
+		def ifds(&block)
+			buf=[]
+
+			if(block.nil?)
+				buf.push(self)
+			else
+				if(yield(dir.value))
+					buf.push(self)
+				end
+			end
+
+			self.each{|dir|
+				if(dir.is_ifd?)
+					buf.push(dir.value.ifds(&block))
+				end
+			}
+			buf
+		end
+
+		def ifd(x=nil)
+			if(x.nil?)
+				self.ifds.flatten
+			elsif(x.kind_of?(String))
+				self.ifds{|ifd|
+					ifd.ifd_name==x
+				}.first
+			elsif(x.kind_of?(Regexp))
+				self.ifds{|ifd|
+					ifd.ifd_name=~x
+				}
+			else
+				self.ifds[x]
+			end
+		end
+
+		def each_ifd(&block)
+			self.each{|dir|
+				if(dir.ifd?)
+					unless(block.nil?)
+						yield buf
+					end
+					ifd.each_ifd(&block)
+				end
+			}
+		end
+
+		def dirs(recursivel=true,&block)
+			buf=[]
+			self.each{|d|
+				if(block.nil?)
+					buf.push(d)
+				else
+					if(yield(d))
+						buf.push(d)
+					end
+				end
+				if(d.is_ifd?&&recursivel)
+					buf+=d.value.dirs(&block)
+				end
+			}
+			buf
+		end
+
+		def dir(x,recursivel=false)
+			if(x.kind_of?(String))
+				self.dirs(recursivel){|d|
+					d.tag_name==x
+				}.first
+			elsif(x.kind_of?(Regexp))
+				self.dirs(recursivel){|d|
+					d.tag_name=~x
+				}.first
+			else
+				self.dirs(recursivel){|d|
+					d.tag==x
+				}.first
+			end
+		end
+
+		def each_dir(recursivel=true,&block)
+			self.each{|d|
+				unless(block.nil?)
+					yield d
+				end
+				if(d.is_ifd?&&recursivel)
+					d.value.each_dir(recursivel&block)
+				end
+			}
+		end
+
+		alias tags dirs
+		alias tag dir
+		alias each_tag each_dir
+
+		def byte_size
+			s=_byte_size_without_thumbnail
+			if(self.respond_to?('jpeg_interchange_format_length'))
+				s+=self.jpeg_interchange_format_length.to_i
+			elsif(self.respond_to?('strip_byte_conunts'))
+				self.strip_byte_conunts.to_a.each{|val|
+					s+=val.to_i
+				}
+			end
+			s
+		end
+
+		def dump(offset=0,endian=:le,has_next_lfd=false)
+			#
+			# set thumbnail offset value
+			#
+			thumbnail_offset=offset+_byte_size_without_thumbnail
+			if(self.respond_to?('jpeg_interchange_format'))
+				self.jpeg_interchange_format.value=thumbnail_offset
+			elsif(self.respond_to?('strip_offsets')&&
+					self.respond_to?('strip_byte_conunts'))
+				buf=[thumbnail_offset]
+				self.strip_byte_conunts.to_a[0..-2].each{|s|
+					buf.push(buf[-1]+s)
+				}
+				self.strip_offsets.value=buf
+			end
+
+			data_offset=offset+
+				self.size*Directory::DIRENT_SIZE+6 # DirEntNum(2)+NextIfd(4)
+
+			head=Data.new_ushort(self.size,endian).to_s
+			data=''
+			self.each{|dir|
+				(h,d)=dir.dump(data_offset,endian)
+				head+=h
+				unless(d.nil?)
+					data+=d
+					data_offset+=d.size
+				end
+			}
+
+			unless(@thumbnail.nil?)
+				@thumbnail.to_a.each{|thumb|
+					data+=thumb.to_s
+				}
+			end
+
+			if(has_next_lfd)
+				head+Data.new_ulong(data_offset,endian).to_s+data
+			else
+				head+Data.new_ulong(0,endian).to_s+data
+			end
+		end
+
+		private
+		def _byte_size_without_thumbnail
+			s=6 # Dir.Entry Num(2bytes)+Pointer to Next IFD(4bytes)
+			self.each{|d|
+				s+=d.byte_size
+			}
+			s
+		end
+	end
+
+	#
+	# Exif
+	#
+	EXIF_ID="Exif\x00\x00"
+	TIFF_MARKER=0x2a
+
+	def initialize(marker=Jpeg::Segment::APP1,data=nil,size=nil,&block)
+		# Exif Header   # "Exif\x00\x00" (6bytes)
+		@endian=:le     # TIFF HEADER  # 'II'|'MM' (2bytes)
+		# tag mark      #              # "0x2a" (2bytes)
+		# offset        #              # offset to data area (4bytes)
+		# data area
+
+		@ifd=[]
+		@is_exif=false
+
+		super
+	end
+	attr_reader :ifd
+
+	def is_exif?
+		@is_exif
+	end
+
+	def parse(data,read_size=nil,&block)
+		x=super
+
+		#
+		# check exif
+		#
+		unless(x[0..5]==EXIF_ID)
+			raise Exif::ParseError,'Not Exif (EXIF Header Not found)'
+			return nil
+		end
+
+		#
+		# check endian
+		#
+		case x[6..7]
+		when "II"
+			@endian=:le
+		when "MM"
+			@endian=:be
+		else
+			raise Exif::ParseError,"Unknown Endian (#{x[6..7]})"
+			return nil
+		end
+		x=Data.new(x[6..-1],@endian)
+
+		#
+		# check exif again
+		#
+		unless(x.get_ushort(2)==TIFF_MARKER)
+			raise Exif::ParseError,'Not Exif (TIFF Marker Not found)'
+			return nil
+		end
+
+		begin
+			offset=x.get_ulong(4)
+			begin
+				@ifd.push(Ifd.new("IFD#{@ifd.size}"))
+				offset=@ifd[-1].parse(x,offset,&block)
+			end until(offset==0)
+		rescue NameError,TypeError
+			raise Exif::ParseError,
+		'Broken Exif: Invalid offset given for pointer to IFD0.'
+		rescue Exif::ParseError
+			raise Exif::ParseError,
+		"Broken Exif: #{$!.to_s}"
+		end
+		@is_exif=true
+		@byte_data='' # to save memory :)
+
+		self
+	end
+
+	def size
+		unless(@is_exif)
+			return super
+		end
+
+		s=16 # data_size(2)+exif_header(6)+byte_alien(2)+
+		#  tag_mark(2)+ifd_offset(4)
+
+		@ifd.each{|ifd|
+			s+=ifd.byte_size
+		}
+		s
+	end
+
+	def byte_data
+		unless(@is_exif)
+			return @byte_data
+		end
+
+		buf=EXIF_ID
+		offset=8 # byte_align(2)+tiff_marker(2)+ifd_offset(4)
+
+		if(@endian==:be)
+			buf+='MM'+[TIFF_MARKER].pack('n')+[offset].pack('N')
+		else
+			buf+='II'+[TIFF_MARKER].pack('v')+[offset].pack('V')
+		end
+		@ifd[0..-2].each{|ifd|
+			d=ifd.dump(offset,@endian,true)
+			offset+=d.size
+			buf+=d
+		}
+		buf+=@ifd[-1].dump(offset,@endian,false)
+
+		buf
+	end
+
+	def ifds(&block)
+		buf=[]
+		@ifd.each{|ifd|
+			buf.push(ifd.ifds(&block))
+		}
+		buf
+	end
+
+	def ifd(x=nil)
+		if(x.nil?)
+			self.ifds.flatten.compact
+		else
+			if(x.kind_of?(String))
+				self.ifds{|ifd|
+					ifd.ifd_name==x
+				}.flatten.compact.first
+			elsif(x.kind_of?(Regexp))
+				self.ifds{|ifd|
+					ifd.ifd_name=~x
+				}.flatten.compact
+			else
+				self.ifds.flatten.compact[x]
+			end
+		end
+	end
+
+	def each_ifd(&block)
+		@ifd.each{|ifd|
+			unless(block.nil?)
+				yield buf
+			end
+			ifd.each_ifd(&block)
+		}
+	end
+
+	def ifd0
+		@ifd[0]
+	end
+	alias image_ifd ifd0
+
+	def ifd1
+		@ifd[1]
+	end
+	alias thumnail_ifd ifd1
+
+	def exif_ifd
+		begin
+			@ifd[0].exif_ifd
+		rescue NameError
+			nil
+		end
+	end
+
+	def interoperability_ifd
+		begin
+			@ifd[0].exif_ifd.interoperability_ifd
+		rescue NameError
+			nil
+		end
+	end
+
+	def dirs(&block)
+		buf=[]
+		@ifd.each{|ifd|
+			buf+=ifd.dirs(&block)
+		}
+		buf
+	end
+
+	def dir(x)
+		if(x.kind_of?(String))
+			self.dirs{|d|
+				d.tag_name==x
+			}
+		elsif(x.kind_of?(Regexp))
+			self.dirs{|d|
+				d.tag_name=~x
+			}
+		else
+			self.dirs{|d|
+				d.tag==x
+			}
+		end
+	end
+
+	def each_dir(&block)
+		@ifd.each{|ifd|
+			ifd.each_tag(&block)
+		}
+	end
+
+	alias tags dirs
+	alias tag dir
+	alias each_tag each_dir
+
+	def thumbnail
+		begin
+			case @ifd[1].thumbnail_type
+			when :jpeg
+				if(@ifd[1].compression==6)
+					@ifd[1].thumbnail_image
+				else
+					nil
+				end
+			when :tiff
+				# TODO
+			end
+		rescue NameError
+			nil
+		end
+	end
+
+	def has_thumbnail?
+		@ifd[1].respond_to?('thumbnail_image')
+	end
+
+	def has_jpeg_thumbnail?
+		begin
+			if(@ifd[1].thumbnail_type==:jpeg)
+				true
+			else
+				false
+			end
+		rescue NameError
+			false
+		end
+	end
+end
+
+class Exif;class Ifd;class Directory
+	Directory.use_proc_for(DateTime,TIME_PARSE_PROC)
+	Directory.use_proc_for(DateTimeOriginal,TIME_PARSE_PROC)
+	Directory.use_proc_for(DateTimeDigitized,TIME_PARSE_PROC)
+end;end;end
Index: /tdiary/branches/upstream/contrib/lib/rjpeg.rb
===================================================================
--- /tdiary/branches/upstream/contrib/lib/rjpeg.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/lib/rjpeg.rb (revision 710)
@@ -0,0 +1,677 @@
+#
+# rjpeg.rb
+#  -- jpeg handling library
+#
+#  NISHI Takao <zophos@koka-in.org>
+#  $Id: rjpeg.rb,v 1.4 2005/07/25 12:48:13 tadatadashi Exp $
+#
+class Jpeg
+    class ParseError<StandardError
+    end
+    
+    #
+    # Jpeg Maker Handling Class
+    #
+    class Segment
+	DELIM ="\xff"
+	IMG   ="\x00" # Not a Segment
+
+	TEM   ="\x01" # Temporary Marker
+	SOF0  ="\xc0" # Baseline DCT (Huffman)
+	SOF1  ="\xc1" # Extended sequential DCT (Huffman)
+	SOF2  ="\xc2" # Progressive DCT (Huffman)
+	SOF3  ="\xc3" # Spatial DCT (Huffman)
+	DHT   ="\xc4" # Definition Huffman Table
+	SOF5  ="\xc5" # Differential sequential DCT (Huffman)
+	SOF6  ="\xc6" # Differential progressive DCT (Huffman)
+	SOF7  ="\xc7" # Differential spatial (Huffman)
+	JPG   ="\xc8" # Reserved
+	SOF9  ="\xc9" # Extended sequential DCT (arithmetic)
+	SOF10 ="\xca" # Progressive DCT (arithmetic)
+	SOF11 ="\xcb" # Spatial lossless DCT (arithmetic)
+	DAC   ="\xcc" # Definition Arithmetic Compress
+	SOF12 ="\xcd" # Differential sequential DCT (arithmetic)
+	SOF13 ="\xce" # DIfferential progressive DCT (arithmetic)
+	SOF14 ="\xcf" # Differential spatial (arithmetic)
+	RST0  ="\xd0" # Restart Marker 0
+	RST1  ="\xd1" # Restart Marker 1
+	RST2  ="\xd2" # Restart Marker 2
+	RST3  ="\xd3" # Restart Marker 3
+	RST4  ="\xd4" # Restart Marker 4
+	RST5  ="\xd5" # Restart Marker 5
+	RST6  ="\xd6" # Restart Marker 6
+	RST7  ="\xd7" # Restart Marker 7
+	SOI   ="\xd8" # Start Of Image
+	EOI   ="\xd9" # End Of Image
+	SOS   ="\xda" # Start Of Scan
+	DQT   ="\xdb" # Definition Quontom Table
+	DNL   ="\xdc" # Definition Number of Line
+	DRI   ="\xdd" # Definition Restart Interval
+	DHP   ="\xde" # Definition Haffman P?
+	EXP   ="\xdf" # Expand segmant
+	APP0  ="\xe0" # Application Marker Segmant 0
+	APP1  ="\xe1" # Application Marker Segmant 1
+	APP2  ="\xe2" # Application Marker Segmant 2
+	APP3  ="\xe3" # Application Marker Segmant 3
+	APP4  ="\xe4" # Application Marker Segmant 4
+	APP5  ="\xe5" # Application Marker Segmant 5
+	APP6  ="\xe6" # Application Marker Segmant 6
+	APP7  ="\xe7" # Application Marker Segmant 7
+	APP8  ="\xe8" # Application Marker Segmant 8
+	APP9  ="\xe9" # Application Marker Segmant 9
+	APP10 ="\xea" # Application Marker Segmant 10
+	APP11 ="\xeb" # Application Marker Segmant 11
+	APP12 ="\xec" # Application Marker Segmant 12
+	APP13 ="\xed" # Application Marker Segmant 13
+	APP14 ="\xee" # Application Marker Segmant 14
+	APP15 ="\xef" # Application Marker Segmant 15
+	JPG0  ="\xf0" # Reserved
+	JPG1  ="\xf1" # Reserved
+	JPG2  ="\xf2" # Reserved
+	JPG3  ="\xf3" # Reserved
+	JPG4  ="\xf4" # Reserved
+	JPG5  ="\xf5" # Reserved
+	JPG6  ="\xf6" # Reserved
+	JPG7  ="\xf7" # Reserved
+	JPG8  ="\xf8" # Reserved
+	JPG9  ="\xf9" # Reserved
+	JPG10 ="\xfa" # Reserved
+	JPG11 ="\xfb" # Reserved
+	JPG12 ="\xfc" # Reserved
+	JPG13 ="\xfd" # Reserved
+	COM   ="\xfe" # Comment
+
+	MARKER_NAME={
+	    "\x00"=>"IMG",
+	    "\x01"=>"TEM",
+	    "\xc0"=>"SOF0",
+	    "\xc1"=>"SOF1",
+	    "\xc2"=>"SOF2",
+	    "\xc3"=>"SOF3",
+	    "\xc4"=>"DHT",
+	    "\xc5"=>"SOF5",
+	    "\xc6"=>"SOF6",
+	    "\xc7"=>"SOF7",
+	    "\xc8"=>"JPG",
+	    "\xc9"=>"SOF9",
+	    "\xca"=>"SOF10",
+	    "\xcb"=>"SOF11",
+	    "\xcc"=>"DAC",
+	    "\xcd"=>"SOF12",
+	    "\xce"=>"SOF13",
+	    "\xcf"=>"SOF14",
+	    "\xd0"=>"RST0",
+	    "\xd1"=>"RST1",
+	    "\xd2"=>"RST2",
+	    "\xd3"=>"RST3",
+	    "\xd4"=>"RST4",
+	    "\xd5"=>"RST5",
+	    "\xd6"=>"RST6",
+	    "\xd7"=>"RST7",
+	    "\xd8"=>"SOI",
+	    "\xd9"=>"EOI",
+	    "\xda"=>"SOS",
+	    "\xdb"=>"DQT",
+	    "\xdc"=>"DNL",
+	    "\xdd"=>"DRI",
+	    "\xde"=>"DHP",
+	    "\xdf"=>"EXP",
+	    "\xe0"=>"APP0",
+	    "\xe1"=>"APP1",
+	    "\xe2"=>"APP2",
+	    "\xe3"=>"APP3",
+	    "\xe4"=>"APP4",
+	    "\xe5"=>"APP5",
+	    "\xe6"=>"APP6",
+	    "\xe7"=>"APP7",
+	    "\xe8"=>"APP8",
+	    "\xe9"=>"APP9",
+	    "\xea"=>"APP10",
+	    "\xeb"=>"APP11",
+	    "\xec"=>"APP12",
+	    "\xed"=>"APP13",
+	    "\xee"=>"APP14",
+	    "\xef"=>"APP15",
+	    "\xf0"=>"JPG0",
+	    "\xf1"=>"JPG1",
+	    "\xf2"=>"JPG2",
+	    "\xf3"=>"JPG3",
+	    "\xf4"=>"JPG4",
+	    "\xf5"=>"JPG5",
+	    "\xf6"=>"JPG6",
+	    "\xf7"=>"JPG7",
+	    "\xf8"=>"JPG8",
+	    "\xf9"=>"JPG9",
+	    "\xfa"=>"JPG10",
+	    "\xfb"=>"JPG11",
+	    "\xfc"=>"JPG12",
+	    "\xfd"=>"JPG13",
+	    "\xfe"=>"COM"
+	}
+	MARKER_NAME.freeze
+
+	#
+	# Format I Segments group
+	# These segments have NO data.
+	#
+	FORMAT_I=[SOI,EOI,RST0,RST1,RST2,RST3,RST4,RST5,RST6,RST7]
+	FORMAT_I.freeze
+
+	#
+	# Jpeg::Segment constructor
+	#
+	def initialize(marker,data=nil,read_size=nil,&block)
+	    @marker=marker            # 1byte
+	    #size                     # 2 bytes
+	    @byte_data=nil            # 
+	    
+	    begin
+		unless(data.nil?)
+		    parse(data,read_size,&block)
+		end
+	    rescue ParseError
+	    end
+	end
+
+	attr_reader :marker,:byte_data
+
+	def =~(x)
+	    self.marker_name=~x
+	end
+
+	def ===(x)
+	    (self.marker_name===x)||(@marker===x)
+	end
+
+	def size
+	    if(@byte_data.nil?)
+		0
+	    else
+		if(@marker==IMG)
+		    @byte_data.size
+		else
+		    @byte_data.size+2 # data_size
+		end
+	    end
+	end
+
+	def data=(x)
+	    if(FORMAT_I.member?(@marker))
+		raise TypeError,
+		    "#{MARKER_NAME[@marker]} must not have no data"
+	    else
+		parse(x)
+	    end
+	end
+
+	def dump
+	    if(@marker==IMG)
+		self.byte_data.to_s
+	    else
+		if(@byte_data.nil?)
+		    DELIM+@marker
+		else
+		    DELIM+@marker+[self.size].pack('n')+self.byte_data.to_s
+		end
+	    end
+	end
+
+	def marker_name
+	    MARKER_NAME[@marker]
+	end
+
+	def parse(data,read_size=nil,&block)
+	    if(@marker==IMG)
+		@byte_data=data
+	    else
+		unless(data.respond_to?('read'))
+		    data=StringIO.new(data.to_s)
+		end
+		if(read_size.nil?)
+		    @byte_data=data.read
+		else
+		    @byte_data=data.read(read_size)
+		end
+	    end
+	end
+    end
+    
+    class StringIO<String
+	def initialize(x)
+	    @cur=0
+	    super
+	end
+	def read(x=nil)
+	    sp=@cur
+	    if(x.nil?)
+		@cur=self.size
+	    else
+		@cur+=x
+	    end
+	    String.new(self[sp..@cur-1])
+	end
+	def rewind
+	    @cur=0
+	end
+	def seek(x,whence=IO::SEEK_SET)
+	    case whence
+	    when IO::SEEK_SET
+		@cur=x
+	    when IO::SEEK_CUR
+	    @cur+=x
+	    when IO::SEEK_END
+		@cur=self.size+x
+	    end
+	end
+    end
+
+    #
+    # Jpeg Handling Class
+    #
+    include Enumerable
+
+    PARSE_HEADER_ONLY=true
+    PARSE_FULL_IMAGE=false
+
+    @@segment_class=Hash.new(Segment)
+    def initialize(file=nil,parse_header_only=PARSE_FULL_IMAGE,&block)
+	@segment_class=@@segment_class.dup
+
+	@segments=[Segment.new(Segment::SOI),Segment.new(Segment::EOI)]
+
+	unless(file.nil?)
+	    self.parse(file,parse_header_only,&block)
+	end
+    end
+
+    def Jpeg.load(file,parse_header_only=false,&block)
+	File.open(file){|f|
+	    Jpeg.new(f,parse_header_only,&block)
+	}
+    end
+    def Jpeg.open(file,parse_header_only=false,&block)
+	File.open(file){|f|
+	    Jpeg.new(f,parse_header_only,&block)
+	}
+    end
+    
+    def parse(f,header_only=PARSE_FULL_IMAGE,&block)
+	unless(f.kind_of?(IO))
+	    f=StringIO.new(f)
+	end
+	
+	@segments.clear
+	
+	#
+	# SOI check
+	#
+	f.rewind
+	x=f.read(2)
+	unless(x==Segment::DELIM+Segment::SOI)
+	    raise ParseError,
+		'Not Jpeg File (SOI Not found)'
+	end
+
+	unless(block.nil?)
+	    yield Segment.new(Segment::SOI)
+	end
+	@segments.push(Segment.new(Segment::SOI))
+
+	begin
+	    segment=read_segment(f,&block)
+	    marker=segment.marker
+	    unless(block.nil?)
+		segment=yield(segment)
+		unless(segment.kind_of?(Segment))
+		    segment=Segment.new(Segment::IMG,segment)
+		end
+	    end
+	    @segments.push(segment)
+	end until(marker==Segment::SOS)
+	
+	if(header_only)
+	    unless(block.nil?)
+		yield(Segment.new(Segment::IMG))
+		yield(Segment.new(Segment::EOI))
+	    end
+	    @segments.push(Segment.new(Segment::IMG))
+	    @segments.push(Segment.new(Segment::EOI))
+
+	    _set_segment_method
+	    return self
+	end
+	
+	i=f.read
+	#
+	# EOI check
+	#
+	eoi=i.index(Segment::DELIM+Segment::EOI)
+	if(eoi.nil?)
+	    raise ParseError,
+		'Broken Jpeg File (EOI Not found)'
+	end
+
+	segment=Segment.new(Segment::IMG,i[0..eoi-1])
+	unless(block.nil?)
+	    segment=yield(segment)
+	    unless(segment.kind_of?(Segment))
+		segment=Segment.new(Segment::IMG,segment)
+	    end
+	end
+	@segments.push(segment)
+	@segments.push(Segment.new(Segment::EOI))
+
+
+	segment=i[eoi+2..-1]
+	if(segment.empty?)
+	    segment=nil
+	else
+	    segment=Segment.new(Segment::IMG,segment)
+	end
+	unless(segment.nil?)
+	    unless(block.nil?)
+		segment=yield(segment)
+		unless(segment.kind_of?(Segment))
+		    image=Segment.new(Segment::IMG,segment)
+		end
+	    end
+	    @segments.push(segment)
+	end
+
+	_set_segment_method
+	return self
+    end
+
+    def imagestream
+	begin
+	    i=@segments.index(self.sos)
+	    @segments[i+1]
+	rescue NameError
+	    nil
+	end
+    end
+
+    def garbage
+	begin
+	    i=@segments.index(self.eoi)
+	    @segments[i+1]
+	rescue NameError
+	    nil
+	end
+    end
+
+    def dump
+	buf=''
+	@segments.compact.each{|s|
+	    begin
+		buf+=s.dump
+	    rescue NameError
+		buf+=s.to_s
+	    end
+	}
+	buf
+    end
+
+    undef max,min,sort
+
+    def each(&block)
+	@segments.each(&block)
+    end
+
+    alias segments to_a
+
+    def index(seg)
+	if(seg.kind_of?(String))
+	    @segments.each_index{|i|
+		if(@segments[i]===seg)
+		    return i
+		end
+	    }
+	    nil
+	else
+	    super
+	end
+    end
+
+    def has_segment?(seg)
+	!self.index(seg).nil?
+    end
+    alias include? has_segment?
+    alias member? has_segment?
+
+    def [](*args)
+	if(args[0].kind_of?(Regexp))
+	    @segments.find_all{|seg|
+		seg=~args[0]
+	    }
+	elsif(args[0].kind_of?(String))
+	    @segments.find{|seg|
+		seg===args[0]
+	    }
+	else
+	    if(args[0].kind_of?(Range))
+		if(args[0].first.kind_of?(String))
+		    f=self.index(args[0].first)
+		    l=self.index(args[0].last)
+		    if(f.nil?||l.nil?)
+			return []
+		    end
+		    args[0]=Range.new(f,l)
+		end
+	    end
+	    @segments[*args]
+	end
+    end
+
+    def []=(*args)
+	value=args.pop
+	unless(value.kind_of?(Segment))
+	    value=Segment.new(Segment::IMG,value)
+	end
+	if(args[0].kind_of?(String))
+	    args[0]=@segments.index(self[args[0]])
+	elsif(args[0].kind_of?(Segment))
+	    args[0]=@segments.index(args[0])
+	elsif(args[0].kind_of?(Range))
+	    if(args[0].first.kind_of?(String))
+		f=self.index(args[0].first)
+		l=self.index(args[0].last)
+		begin
+		    args[0]=Range.new(f,l)
+		rescue ArgumentError
+		    raise $!.to_s
+		    return nil
+		end
+	    end
+	end
+
+	ret=@segments[*args]=value
+
+	_set_segment_method
+	ret
+    end
+
+    def insert_at(pos,x)
+	self[pos,0]=x
+    end
+
+    def insert_after_soi(x)
+	self[self.index(Segment::SOI)+1,0]=x
+    end
+    
+    def insert_befor_sos(x)
+	self[self.index(Segment::SOS),0]=x
+    end
+    alias insert insert_befor_sos
+
+    def insert_after_eoi(x)
+	self[self.index(Segment::EOI)+1,0]=x
+    end
+    alias garbage= insert_after_eoi
+
+    def delete(seg,&block)
+	ret=nil
+	if(seg.kind_of?(Regexp))
+	    @segments.delete_if{|s|
+		if(s=~seg)
+		    ret=seg
+		    true
+		else
+		    false
+		end
+	    }
+	elsif(seg.kind_of?(String))
+	    @segments.delete_if{|s|
+		if(s===seg)
+		    ret=seg
+		else
+		    false
+		end
+	    }
+	else
+	    ret=@segments.delete(seg)
+	end
+
+	if(ret.nil?)
+	    @segments.each(&block)
+	else
+	    _set_segment_method
+	    ret
+	end
+    end
+
+    def delete_at(pos)
+	ret=@segments.delete_at(pos)
+	unless(ret.nil?)
+	    _set_segment_method
+	end
+	ret
+    end
+
+    def delete_if(&block)
+	ret=@segments.delete_if(&block)
+	_set_segment_method
+	ret
+    end
+
+    def compact
+	@segments.delete_if{|s|
+	    s.nil?||((s.marker==Segment::IMG)&&s.byte_data.nil?)
+	}
+	@segments.dup
+    end
+
+    def delete_garbage
+	ret=nil
+	pos=self.index(Segment::EOI)
+	unless(pos.nil?)
+	    ret=@segments[pos+1..-1]
+	    @segments=@segments[0..pos]
+	end
+	ret
+    end
+    alias trim_garbage delete_garbage
+
+    def use_class_for(segment,data_class,force_replace=false)
+	if((data_class.class==Class)&&
+	   (data_class.ancestors.include?(Segment)))
+	    @segment_class[segment]=data_class
+
+	    #
+	    # re-parse
+	    #
+	    if(force_replace)
+		i=@segments.each_index{|i|
+		    if(s.marker==segment)
+			return i
+		    end
+		}
+		@segments[i]=data_class.new(@segments[i].marker,
+					    @segments[i].byte_data)
+	    end
+	else
+	    raise TypeError,
+		'Must be descendants of Jpeg::Segment Class'
+	end
+    end
+
+    def Jpeg.use_class_for(segment,data_class)
+	if((data_class.class==Class)&&
+	   (data_class.ancestors.include?(Segment)))
+	    @@segment_class[segment]=data_class
+	else
+	    raise TypeError, 
+		'Must be descendants of Jpeg::Segment Class'
+	end
+    end
+
+    private
+    def read_ushort(f)
+	f.read(2).unpack('n').first
+    end
+    def read_ulong(f)
+	f.read(4).unpack('N').first
+    end
+    def read_segment(f,&block)
+	m=f.read(2)
+	unless(m[0..0]==Segment::DELIM)
+	    raise "Irregular Segment"
+	end
+	m=m[1..1]
+	s=read_ushort(f)-2
+	@segment_class[m].new(m,f,s,&block)
+    end
+
+    def _set_segment_method
+	# get self singular methods list
+	singular_methods=self.methods-self.class.new.methods
+
+	# create methods without SOI and EOI
+	#
+	@segments.each_index{|i|
+	    begin
+		unless(@segments[i+1].marker==Segment::IMG)
+		    name=@segments[i+1].marker_name.downcase
+		    self.instance_eval("def #{name};@segments[#{i+1}];end")
+		    singular_methods.delete(name)
+		end
+	    rescue NameError
+	    end
+	}
+
+	# delete not difined singular methods
+	singular_methods.each{|m|
+	    self.instance_eval("undef #{m}")
+	}
+    end
+end
+
+class Jpeg
+    class SOFData<Segment
+	def initialize(marker,io,size)
+	    @width=0
+	    @height=0
+	    super
+	end
+	attr_reader :width,:height
+	
+	def parse(io,size)
+	    super
+	    (@height,@width)=@byte_data.unpack('xnn')
+	end
+    end
+    
+    Jpeg.use_class_for(Segment::SOF0,SOFData)
+    Jpeg.use_class_for(Segment::SOF1,SOFData)
+    Jpeg.use_class_for(Segment::SOF2,SOFData)
+    Jpeg.use_class_for(Segment::SOF3,SOFData)
+    Jpeg.use_class_for(Segment::SOF5,SOFData)
+    Jpeg.use_class_for(Segment::SOF6,SOFData)
+    Jpeg.use_class_for(Segment::SOF7,SOFData)
+    Jpeg.use_class_for(Segment::SOF9,SOFData)
+    Jpeg.use_class_for(Segment::SOF10,SOFData)
+    Jpeg.use_class_for(Segment::SOF11,SOFData)
+    Jpeg.use_class_for(Segment::SOF12,SOFData)
+    Jpeg.use_class_for(Segment::SOF13,SOFData)
+    Jpeg.use_class_for(Segment::SOF14,SOFData)
+end
Index: /tdiary/branches/upstream/contrib/lib/json.rb
===================================================================
--- /tdiary/branches/upstream/contrib/lib/json.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/lib/json.rb (revision 710)
@@ -0,0 +1,718 @@
+# = json - JSON library for Ruby
+#
+# == Description
+#
+# == Author
+#
+# Florian Frank <mailto:flori@ping.de>
+#
+# == License
+#
+# This is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License Version 2 as published by the Free
+# Software Foundation: www.gnu.org/copyleft/gpl.html
+#
+# == Download
+#
+# The latest version of this library can be downloaded at
+#
+# * http://rubyforge.org/frs?group_id=953
+#
+# Online Documentation should be located at
+#
+# * http://json.rubyforge.org
+#
+# == Examples
+#
+# To create a JSON string from a ruby data structure, you
+# can call JSON.unparse (or JSON.generate) like that:
+#
+#  json = JSON.unparse [1, 2, {"a"=>3.141}, false, true, nil, 4..10]
+#  # => "[1,2,{\"a\":3.141},false,true,null,\"4..10\"]"
+#
+# It's also possible to call the #to_json method directly.
+#
+#  json = [1, 2, {"a"=>3.141}, false, true, nil, 4..10].to_json
+#  # => "[1,2,{\"a\":3.141},false,true,null,\"4..10\"]"
+#
+# To get back a ruby data structure, you have to call
+# JSON.parse on the JSON string:
+#
+#  JSON.parse json
+#  # => [1, 2, {"a"=>3.141}, false, true, nil, "4..10"]
+# 
+# Note, that the range from the original data structure is a simple
+# string now. The reason for this is, that JSON doesn't support ranges
+# or arbitrary classes. In this case the json library falls back to call
+# Object#to_json, which is the same as #to_s.to_json.
+#
+# It's possible to extend JSON to support serialization of arbitray classes by
+# simply implementing a more specialized version of the #to_json method, that
+# should return a JSON object (a hash converted to JSON with #to_json)
+# like this (don't forget the *a for all the arguments):
+#
+#  class Range
+#    def to_json(*a)
+#      {
+#        'json_class'   => self.class.name,
+#        'data'         => [ first, last, exclude_end? ]
+#      }.to_json(*a)
+#    end
+#  end
+#
+# The hash key 'json_class' is the class, that will be asked to deserialize the
+# JSON representation later. In this case it's 'Range', but any namespace of
+# the form 'A::B' or '::A::B' will do. All other keys are arbitrary and can be
+# used to store the necessary data to configure the object to be deserialized.
+#
+# If a the key 'json_class' is found in a JSON object, the JSON parser checks
+# if the given class responds to the json_create class method. If so, it is
+# called with the JSON object converted to a Ruby hash. So a range can
+# be deserialized by implementing Range.json_create like this:
+# 
+#  class Range
+#    def self.json_create(o)
+#      new(*o['data'])
+#    end
+#  end
+#
+# Now it possible to serialize/deserialize ranges as well:
+#
+#  json = JSON.unparse [1, 2, {"a"=>3.141}, false, true, nil, 4..10]
+#  # => "[1,2,{\"a\":3.141},false,true,null,{\"json_class\":\"Range\",\"data\":[4,10,false]}]"
+#  JSON.parse json
+#  # => [1, 2, {"a"=>3.141}, false, true, nil, 4..10]
+#
+# JSON.unparse always creates the shortes possible string representation of a
+# ruby data structure in one line. This good for data storage or network
+# protocols, but not so good for humans to read. Fortunately there's
+# also JSON.pretty_unparse (or JSON.pretty_generate) that creates a more
+# readable output:
+#
+#  puts JSON.pretty_unparse([1, 2, {"a"=>3.141}, false, true, nil, 4..10])
+#  [
+#    1,
+#    2,
+#    {
+#      "a": 3.141
+#    },
+#    false,
+#    true,
+#    null,
+#    {
+#      "json_class": "Range",
+#      "data": [
+#        4,
+#        10,
+#        false
+#      ]
+#    }
+#  ]
+#
+# There are also the methods Kernel#j for unparse, and Kernel#jj for
+# pretty_unparse output to the console, that work analogous to Kernel#p and
+# Kernel#pp.
+#
+
+require 'strscan'
+
+# This module is the namespace for all the JSON related classes. It also 
+# defines some module functions to expose a nicer API to users, instead
+# of using the parser and other classes directly.
+module JSON
+  # The base exception for JSON errors.
+  JSONError             = Class.new StandardError
+
+  # This exception is raise, if a parser error occurs.
+  ParserError           = Class.new JSONError
+
+  # This exception is raise, if a unparser error occurs.
+  UnparserError         = Class.new JSONError
+
+  # If a circular data structure is encountered while unparsing
+  # this exception is raised.
+  CircularDatastructure = Class.new UnparserError
+
+  class << self
+    # Switches on Unicode support, if _enable_ is _true_. Otherwise switches
+    # Unicode support off.
+    def support_unicode=(enable)
+      @support_unicode = enable
+    end
+
+    # Returns _true_ if JSON supports unicode, otherwise _false_ is returned.
+    #
+    # If loading of the iconv library fails, or it doesn't support utf8/utf16
+    # encoding, this will be set to false, as a fallback.
+    def support_unicode?
+      !!@support_unicode
+    end
+  end
+  JSON.support_unicode = true # default, however it's possible to switch off
+                              # full unicode support, if non-ascii bytes should be
+                              # just passed through.
+
+  begin
+    require 'iconv'
+    # An iconv instance to convert from UTF8 to UTF16 Big Endian.
+    UTF16toUTF8 = Iconv.new('utf-8', 'utf-16be')
+    # An iconv instance to convert from UTF16 Big Endian to UTF8.
+    UTF8toUTF16 = Iconv.new('utf-16be', 'utf-8'); UTF8toUTF16.iconv('no bom')
+  rescue Errno::EINVAL, Iconv::InvalidEncoding
+    # Iconv doesn't support big endian utf-16. Let's try to hack this manually
+    # into the converters.
+    begin
+      old_verbose = $VERBOSE
+      $VERBOSE = nil
+      # An iconv instance to convert from UTF8 to UTF16 Big Endian.
+      UTF16toUTF8 = Iconv.new('utf-8', 'utf-16')
+      # An iconv instance to convert from UTF16 Big Endian to UTF8.
+      UTF8toUTF16 = Iconv.new('utf-16', 'utf-8'); UTF8toUTF16.iconv('no bom')
+      if UTF8toUTF16.iconv("\xe2\x82\xac") == "\xac\x20"
+        swapper = Class.new do
+          def initialize(iconv)
+            @iconv = iconv
+          end
+
+          def iconv(string)
+            result = @iconv.iconv(string)
+            JSON.swap!(result)
+          end
+        end
+        UTF8toUTF16 = swapper.new(UTF8toUTF16)
+      end
+      if UTF16toUTF8.iconv("\xac\x20") == "\xe2\x82\xac"
+        swapper = Class.new do
+          def initialize(iconv)
+            @iconv = iconv
+          end
+
+          def iconv(string)
+            string = JSON.swap!(string.dup)
+            @iconv.iconv(string)
+          end
+        end
+        UTF16toUTF8 = swapper.new(UTF16toUTF8)
+      end
+    rescue Errno::EINVAL, Iconv::InvalidEncoding
+      # Enforce disabling of unicode support, if iconv doesn't support
+      # UTF8/UTF16 at all.
+      JSON.support_unicode = false
+    ensure
+      $VERBOSE = old_verbose
+    end
+  rescue LoadError
+    # Enforce disabling of unicode support, if iconv doesn't exist.
+    JSON.support_unicode = false
+  end
+
+  # Swap consecutive bytes in string in place.
+  def self.swap!(string)
+    0.upto(string.size / 2) do |i|
+      break unless string[2 * i + 1]
+      string[2 * i], string[2 * i + 1] = string[2 * i + 1], string[2 * i]
+    end
+    string
+  end
+
+  # This class implements the JSON parser that is used to parse a JSON string
+  # into a Ruby data structure.
+  class Parser < StringScanner
+    STRING                = /"((?:[^"\\]|\\.)*)"/
+    INTEGER               = /-?(?:0|[1-9]\d*)/
+    FLOAT                 = /-?(?:0|[1-9]\d*)\.(\d+)(?i:e[+-]?\d+)?/
+    OBJECT_OPEN           = /\{/
+    OBJECT_CLOSE          = /\}/
+    ARRAY_OPEN            = /\[/
+    ARRAY_CLOSE           = /\]/
+    PAIR_DELIMITER        = /:/
+    COLLECTION_DELIMITER  = /,/
+    TRUE                  = /true/
+    FALSE                 = /false/
+    NULL                  = /null/
+    IGNORE                = %r(
+      (?:
+        //[^\n\r]*[\n\r]| # line comments
+        /\*               # c-style comments
+          (?:
+            [^*/]|        # normal chars
+            /[^*]|        # slashes that do not start a nested comment
+            \*[^/]|       # asterisks that do not end this comment
+            /(?=\*/)      # single slash before this comment's end 
+          )*
+        \*/               # the end of this comment
+        |[ \t\r\n]+       # whitespaces: space, horicontal tab, lf, cr
+      )+
+    )mx
+
+    UNPARSED = Object.new
+
+    # Parses the current JSON string and returns the complete data structure
+    # as a result.
+    def parse
+      reset
+      obj = nil
+      until eos?
+        case
+        when scan(OBJECT_OPEN)
+          obj and raise ParserError, "source '#{peek(20)}' not in JSON!"
+          obj = parse_object
+        when scan(ARRAY_OPEN)
+          obj and raise ParserError, "source '#{peek(20)}' not in JSON!"
+          obj = parse_array
+        when skip(IGNORE)
+          ;
+        else
+          raise ParserError, "source '#{peek(20)}' not in JSON!"
+        end
+      end
+      obj or raise ParserError, "source did not contain any JSON!"
+      obj
+    end
+
+    private
+
+    def parse_string
+      if scan(STRING)
+        return '' if self[1].empty?
+        self[1].gsub(%r(\\(?:[\\bfnrt"/]|u([A-Fa-f\d]{4})))) do
+          case $~[0]
+          when '\\"'  then '"'
+          when '\\\\' then '\\'
+          when '\\/'  then '/'
+          when '\\b'  then "\b"
+          when '\\f'  then "\f"
+          when '\\n'  then "\n"
+          when '\\r'  then "\r"
+          when '\\t'  then "\t"
+          else
+            if JSON.support_unicode? and $KCODE == 'UTF8'
+              JSON.utf16_to_utf8($~[1])
+            else
+              # if utf8 mode is switched off or unicode not supported, try to
+              # transform unicode \u-notation to bytes directly:
+              $~[1].to_i(16).chr
+            end
+          end
+        end
+      else
+        UNPARSED
+      end
+    end
+
+    def parse_value
+      case
+      when scan(FLOAT)
+        Float(self[0].sub(/\.([eE])/, '.0\1'))
+      when scan(INTEGER)
+        Integer(self[0])
+      when scan(TRUE)
+        true
+      when scan(FALSE)
+        false
+      when scan(NULL)
+        nil
+      when (string = parse_string) != UNPARSED
+        string
+      when scan(ARRAY_OPEN)
+        parse_array
+      when scan(OBJECT_OPEN)
+        parse_object
+      else
+        UNPARSED
+      end
+    end
+
+    def parse_array
+      result = []
+      until eos?
+        case
+        when (value = parse_value) != UNPARSED
+          result << value
+          skip(IGNORE)
+          unless scan(COLLECTION_DELIMITER) or match?(ARRAY_CLOSE)
+            raise ParserError, "expected ',' or ']' in array at '#{peek(20)}'!"
+          end
+        when scan(ARRAY_CLOSE)
+          break
+        when skip(IGNORE)
+          ;
+        else
+          raise ParserError, "unexpected token in array at '#{peek(20)}'!"
+        end
+      end
+      result
+    end
+
+    def parse_object
+      result = {}
+      until eos?
+        case
+        when (string = parse_string) != UNPARSED
+          skip(IGNORE)
+          unless scan(PAIR_DELIMITER)
+            raise ParserError, "expected ':' in object at '#{peek(20)}'!"
+          end
+          skip(IGNORE)
+          unless (value = parse_value).equal? UNPARSED
+            result[string] = value
+            skip(IGNORE)
+            unless scan(COLLECTION_DELIMITER) or match?(OBJECT_CLOSE)
+              raise ParserError,
+                "expected ',' or '}' in object at '#{peek(20)}'!"
+            end
+          else
+            raise ParserError, "expected value in object at '#{peek(20)}'!"
+          end
+        when scan(OBJECT_CLOSE)
+          if klassname = result['json_class']
+            klass = klassname.sub(/^:+/, '').split(/::/).inject(Object) do |p,k|
+              p.const_get(k) rescue nil
+            end
+            break unless klass and klass.json_creatable?
+            result = klass.json_create(result)
+          end
+          break
+        when skip(IGNORE)
+          ;
+        else
+          raise ParserError, "unexpected token in object at '#{peek(20)}'!"
+        end
+      end
+      result
+    end
+  end
+
+  # This class is used to create State instances, that are use to hold data
+  # while unparsing a Ruby data structure into a JSON string.
+  class State
+    # Creates a State object from _opts_, which ought to be Hash to create a
+    # new State instance configured by opts, something else to create an
+    # unconfigured instance. If _opts_ is a State object, it is just returned.
+    def self.from_state(opts)
+      case opts
+      when self
+        opts
+      when Hash
+        new(opts)
+      else
+        new
+      end
+    end
+
+    # Instantiates a new State object, configured by _opts_.
+    def initialize(opts = {})
+      @indent     = opts[:indent]     || ''
+      @space      = opts[:space]      || ''
+      @object_nl  = opts[:object_nl]  || ''
+      @array_nl   = opts[:array_nl]   || ''
+      @seen       = {}
+    end
+
+    # This string is used to indent levels in the JSON string.
+    attr_accessor :indent
+
+    # This string is used to include a space between the tokens in a JSON
+    # string.
+    attr_accessor :space
+
+    # This string is put at the end of a line that holds a JSON object (or
+    # Hash).
+    attr_accessor :object_nl
+
+    # This string is put at the end of a line that holds a JSON array.
+    attr_accessor :array_nl
+
+    # Returns _true_, if _object_ was already seen during this Unparsing run. 
+    def seen?(object)
+      @seen.key?(object.__id__)
+    end
+
+    # Remember _object_, to find out if it was already encountered (to find out
+    # if a cyclic data structure is unparsed). 
+    def remember(object)
+      @seen[object.__id__] = true
+    end
+
+    # Forget _object_ for this Unparsing run.
+    def forget(object)
+      @seen.delete object.__id__
+    end
+  end
+
+  module_function
+
+  # Convert _string_ from UTF8 encoding to UTF16 (big endian) encoding and
+  # return it.
+  def utf8_to_utf16(string)
+    JSON::UTF8toUTF16.iconv(string).unpack('H*')[0]
+  end
+
+  # Convert _string_ from UTF16 (big endian) encoding to UTF8 encoding and
+  # return it.
+  def utf16_to_utf8(string)
+    bytes = '' << string[0, 2].to_i(16) << string[2, 2].to_i(16)
+    JSON::UTF16toUTF8.iconv(bytes)
+  end
+
+  # Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with
+  # UTF16 big endian characters as \u????, and return it.
+  def utf8_to_json(string)
+    i, n, result = 0, string.size, ''
+    while i < n
+      char = string[i]
+      case
+      when char == ?\b then result << '\b'
+      when char == ?\t then result << '\t'
+      when char == ?\n then result << '\n'
+      when char == ?\f then result << '\f'
+      when char == ?\r then result << '\r'
+      when char == ?"  then result << '\"'
+      when char == ?\\ then result << '\\\\'
+      when char == ?/ then result << '\/'
+      when char.between?(0x0, 0x1f) then result << "\\u%04x" % char
+      when char.between?(0x20, 0x7f) then result << char
+      when !(JSON.support_unicode? && $KCODE == 'UTF8')
+        # if utf8 mode is switched off or unicode not supported, just pass
+        # bytes through:
+        result << char
+      when char & 0xe0 == 0xc0
+        result << '\u' << utf8_to_utf16(string[i, 2])
+        i += 1
+      when char & 0xf0 == 0xe0
+        result << '\u' << utf8_to_utf16(string[i, 3])
+        i += 2
+      when char & 0xf8 == 0xf0
+        result << '\u' << utf8_to_utf16(string[i, 4])
+        i += 3
+      when char & 0xfc == 0xf8
+        result << '\u' << utf8_to_utf16(string[i, 5])
+        i += 4
+      when char & 0xfe == 0xfc
+        result << '\u' << utf8_to_utf16(string[i, 6])
+        i += 5
+      else
+        raise JSON::UnparserError, "Encountered unknown UTF-8 byte: %x!" % char
+      end
+      i += 1
+    end
+    result
+  end
+
+  # Parse the JSON string _source_ into a Ruby data structure and return it.
+  def parse(source)
+    Parser.new(source).parse
+  end
+
+  # Unparse the Ruby data structure _obj_ into a single line JSON string and
+  # return it. _state_ is a JSON::State object, that can be used to configure
+  # the output further.
+  def unparse(obj, state = nil)
+    obj.to_json(JSON::State.from_state(state))
+  end
+
+  alias generate unparse
+
+  # Unparse the Ruby data structure _obj_ into a JSON string and return it.
+  # The returned string is a prettier form of the string returned by #unparse.
+  def pretty_unparse(obj)
+    state = JSON::State.new(
+      :indent     => '  ',
+      :space      => ' ',
+      :object_nl  => "\n",
+      :array_nl   => "\n"
+    )
+    obj.to_json(state)
+  end
+
+  alias pretty_generate pretty_unparse
+end
+
+class Object
+  # Converts this object to a string (calling #to_s), converts
+  # it to a JSON string, and returns the result. This is a fallback, if no
+  # special method #to_json was defined for some object.
+  # _state_ is a JSON::State object, that can also be used
+  # to configure the produced JSON string output further.
+
+  def to_json(*) to_s.to_json end
+end
+
+class Hash
+  # Returns a JSON string containing a JSON object, that is unparsed from
+  # this Hash instance.
+  # _state_ is a JSON::State object, that can also be used to configure the
+  # produced JSON string output further.
+  # _depth_ is used to find out nesting depth, to indent accordingly.
+  def to_json(state = nil, depth = 0)
+    state = JSON::State.from_state(state)
+    json_check_circular(state) { json_transform(state, depth) }
+  end
+
+  private
+
+  def json_check_circular(state)
+    if state
+      state.seen?(self) and raise JSON::CircularDatastructure,
+          "circular data structures not supported!"
+      state.remember self
+    end
+    yield
+  ensure
+    state and state.forget self
+  end
+
+  def json_shift(state, depth)
+    state and not state.object_nl.empty? or return ''
+    state.indent * depth
+  end
+
+  def json_transform(state, depth)
+    delim = ','
+    delim << state.object_nl if state
+    result = '{'
+    result << state.object_nl if state
+    result << map { |key,value|
+      json_shift(state, depth + 1) <<
+        key.to_s.to_json(state, depth + 1) <<
+        ':' << state.space << value.to_json(state, depth + 1)
+    }.join(delim)
+    result << state.object_nl if state
+    result << json_shift(state, depth)
+    result << '}'
+    result
+  end
+end
+
+class Array
+  # Returns a JSON string containing a JSON array, that is unparsed from
+  # this Array instance.
+  # _state_ is a JSON::State object, that can also be used to configure the
+  # produced JSON string output further.
+  # _depth_ is used to find out nesting depth, to indent accordingly.
+  def to_json(state = nil, depth = 0)
+    state = JSON::State.from_state(state)
+    json_check_circular(state) { json_transform(state, depth) }
+  end
+
+  private
+
+  def json_check_circular(state)
+    if state
+      state.seen?(self) and raise JSON::CircularDatastructure,
+        "circular data structures not supported!"
+      state.remember self
+    end
+    yield
+  ensure
+    state and state.forget self
+  end
+
+  def json_shift(state, depth)
+    state and not state.array_nl.empty? or return ''
+    state.indent * depth
+  end
+
+  def json_transform(state, depth)
+    delim = ','
+    delim << state.array_nl if state
+    result = '['
+    result << state.array_nl if state
+    result << map { |value|
+      json_shift(state, depth + 1) << value.to_json(state, depth + 1)
+    }.join(delim)
+    result << state.array_nl if state
+    result << json_shift(state, depth) 
+    result << ']'
+    result
+  end
+end
+
+class Integer
+  # Returns a JSON string representation for this Integer number.
+  def to_json(*) to_s end
+end
+
+class Float
+  # Returns a JSON string representation for this Float number.
+  def to_json(*) to_s end
+end
+
+class String
+  # This string should be encoded with UTF-8 (if JSON unicode support is
+  # enabled). A call to this method returns a JSON string
+  # encoded with UTF16 big endian characters as \u????. If
+  # JSON.support_unicode? is false only control characters are encoded this
+  # way, all 8-bit bytes are just passed through.
+  def to_json(*)
+    '"' << JSON::utf8_to_json(self) << '"'
+  end
+
+  # Raw Strings are JSON Objects (the raw bytes are stored in an array for the
+  # key "raw"). The Ruby String can be created by this class method.
+  def self.json_create(o)
+    o['raw'].pack('C*')
+  end
+
+  # This method creates a raw object, that can be nested into other data
+  # structures and will be unparsed as a raw string.
+  def to_json_raw_object
+    {
+      'json_class'  => self.class.name,
+      'raw'         => self.unpack('C*'),
+    }
+  end
+
+  # This method should be used, if you want to convert raw strings to JSON
+  # instead of UTF-8 strings, e. g. binary data (and JSON Unicode support is
+  # enabled).
+  def to_json_raw(*args)
+    to_json_raw_object.to_json(*args)
+  end
+end
+
+class TrueClass
+  # Returns a JSON string for true: 'true'.
+  def to_json(*) to_s end
+end
+
+class FalseClass
+  # Returns a JSON string for false: 'false'.
+  def to_json(*) to_s end
+end
+
+class NilClass
+  # Returns a JSON string for nil: 'null'.
+  def to_json(*) 'null' end
+end
+
+module Kernel
+  # Outputs _objs_ to STDOUT as JSON strings in the shortest form, that is in
+  # one line.
+  def j(*objs)
+    objs.each do |obj|
+      puts JSON::generate(obj)
+    end
+    nil
+  end
+
+  # Ouputs _objs_ to STDOUT as JSON strings in a pretty format, with
+  # indentation and over many lines.
+  def jj(*objs)
+    objs.each do |obj|
+      puts JSON::pretty_generate(obj)
+    end
+    nil
+  end
+end
+
+class Class
+  # Returns true, if this class can be used to create an instance
+  # from a serialised JSON string. The class has to implement a class
+  # method _json_create_ that expects a hash as first parameter, which includes
+  # the required data.
+  def json_creatable?
+    respond_to?(:json_create)
+  end
+end
+  # vim: set et sw=2 ts=2:
Index: /tdiary/branches/upstream/contrib/lib/wgs2tky.rb
===================================================================
--- /tdiary/branches/upstream/contrib/lib/wgs2tky.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/lib/wgs2tky.rb (revision 710)
@@ -0,0 +1,93 @@
+#
+# wgs2tky.rb
+#  -- GPS data converter
+#
+# kp<kp@mmho.no-ip.org>
+# Destributed under the GPL
+
+class Wgs2Tky
+
+  Pi = Math::PI
+  Rd = Pi/180
+
+  # WGS84
+  A = 6378137.0                # 赤道半径
+  F = 1/298.257223563          # 扁平率
+  E2 = F*2 - F*F               # 第一離心率
+
+  # Tokyo
+  A_ = 6378137.0 - 739.845     # 6377397.155
+  F_ = 1/298.257223563 - 0.000010037483
+                               # 1 / 299.152813
+  E2_ = F_*2 - F_*F_
+
+  Dx = +128
+  Dy = -481
+  Dz = -664	
+
+  def Wgs2Tky.conv!(lat,lon,h = 0)
+    b = lat[0].to_f + lat[1].to_f/60 + lat[2].to_f/3600
+    l = lon[0].to_f + lon[1].to_f/60 + lon[2].to_f/3600
+		
+    (x,y,z) = Wgs2Tky._llh2xyz(b,l,h,A,E2)
+		
+    x+=Dx
+    y+=Dy
+    z+=Dz
+
+    (b,l,h) = Wgs2Tky._xyz2llh(x,y,z,A_,E2_)
+
+    lat[0..2]=Wgs2Tky._deg2gdms(b)
+    lon[0..2]=Wgs2Tky._deg2gdms(l)
+  end
+	
+  private
+
+  include Math
+  extend Math
+
+  def Wgs2Tky._llh2xyz(b,l,h,a,e2)
+
+    b *= Rd
+    l *= Rd
+		
+    sb = sin(b)
+    cb = cos(b)
+
+    rn = a / Math.sqrt(1-e2*sb*sb)
+    
+    x = (rn+h)*cb*cos(l)
+    y = (rn+h)*cb*sin(l)
+    z = (rn*(1-e2)+h) * sb
+		
+    return x,y,z
+  end
+
+  def Wgs2Tky._xyz2llh(x,y,z,a,e2)
+
+    bda = sqrt(1-e2)
+	
+    po = sqrt(x*x+y*y)
+    t = atan2(z,po*bda)
+    st = sin(t)
+    ct = cos(t)
+    b = atan2(z+e2*a/bda*st*st*st,po-e2*a*ct*ct*ct)
+    l = atan2(y,x)
+
+    sb = sin(b)
+    rn = a / sqrt(1-e2*sb*sb)
+    h = po / cos(b) - rn
+		
+    return b/Rd,l/Rd,h
+  end
+
+  def Wgs2Tky._deg2gdms(deg)
+    sf = deg*3600
+    s = sf.to_i%60
+    m = (sf/60).to_i%60
+    d = (sf/3600).to_i
+    s += sf - sf.to_i	
+    return d,m,s
+  end
+end
+
Index: /tdiary/branches/upstream/contrib/spec/google_analytics_spec.rb
===================================================================
--- /tdiary/branches/upstream/contrib/spec/google_analytics_spec.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/spec/google_analytics_spec.rb (revision 710)
@@ -0,0 +1,44 @@
+$:.unshift(File.dirname(__FILE__))
+require 'spec_helper'
+
+describe "google_analytics plugin" do
+	def setup_google_analytics_plugin(profile_id)
+		fake_plugin(:google_analytics) { |plugin|
+			plugin.conf['google_analytics.profile'] = profile_id
+		}
+	end
+
+	describe "should render javascript" do
+		before do
+			@plugin = setup_google_analytics_plugin('53836-1')
+		end
+		
+		it "for footer" do
+			snippet = @plugin.footer_proc
+			snippet.should == expected_html_footer_snippet
+		end
+	end
+
+	describe "should not render when profile_id is empty" do
+		before do
+			@plugin = setup_google_analytics_plugin(nil)
+		end
+		
+		it "for footer" do
+			snippet = @plugin.footer_proc
+			snippet.should be_empty
+		end
+	end
+
+	def expected_html_footer_snippet
+		expected = <<-SCRIPT
+		<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
+		</script>
+		<script type="text/javascript">
+		_uacct = "UA-53836-1";
+		urchinTracker();
+		</script>
+		SCRIPT
+		expected.gsub( /^\t/, '' ).chomp
+	end
+end
Index: /tdiary/branches/upstream/contrib/spec/jdate_spec.rb
===================================================================
--- /tdiary/branches/upstream/contrib/spec/jdate_spec.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/spec/jdate_spec.rb (revision 710)
@@ -0,0 +1,33 @@
+$:.unshift(File.dirname(__FILE__))
+require 'spec_helper'
+require 'time'
+
+describe "jdate plugin" do
+
+	with_fixtures :date => :jwday do
+    def setup_jdate_plugin(date)
+      fake_plugin(:jdate) { |plugin|
+        plugin.date = date
+      }
+    end
+
+    filters({
+      :date => lambda {|val| Time.parse(val) },
+    })
+
+    it ':jwday' do |date, jwday|
+      setup_jdate_plugin(date).date.strftime('%J').should == jwday
+    end
+
+    set_fixtures([
+      [ '20080121' => '月'],
+      [ '20080122' => '火'],
+      [ '20080123' => '水'],
+      [ '20080124' => '木'],
+      [ '20080125' => '金'],
+      [ '20080126' => '土'],
+      [ '20080127' => '日'],
+    ])
+	end
+end
+
Index: /tdiary/branches/upstream/contrib/spec/jyear_spec.rb
===================================================================
--- /tdiary/branches/upstream/contrib/spec/jyear_spec.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/spec/jyear_spec.rb (revision 710)
@@ -0,0 +1,29 @@
+$:.unshift(File.dirname(__FILE__))
+require 'spec_helper'
+require 'time'
+
+describe "jyear plugin" do
+  with_fixtures :date => :jyear do
+    def setup_jyear_plugin(date)
+      fake_plugin(:jyear) { |plugin|
+        plugin.date = date
+      }
+    end
+
+    it 'in :date' do |date, jyear|
+      setup_jyear_plugin(date).date.strftime('%K').should == jyear
+    end
+
+    filters({
+      :date => lambda {|val| Time.parse(val) },
+    })
+
+    set_fixtures([
+      ['1925/01/01' => '昔々'],
+      ['1926/12/25' => '昭和元年'],
+      ['1927/01/01' => '昭和2' ],
+      ['1989/01/08' => '平成元年' ],
+      ['1990/01/01' => '平成2' ],
+    ])
+  end
+end
Index: /tdiary/branches/upstream/contrib/spec/opensearch_ad_spec.rb
===================================================================
--- /tdiary/branches/upstream/contrib/spec/opensearch_ad_spec.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/spec/opensearch_ad_spec.rb (revision 710)
@@ -0,0 +1,50 @@
+$:.unshift(File.dirname(__FILE__))
+require 'spec_helper'
+
+describe "opensearch_ad plugin w/" do
+	def setup_opensearch_ad_plugin(title, xml, mode)
+		fake_plugin(:opensearch_ad) { |plugin|
+			plugin.mode = mode
+			plugin.conf['opensearch.title'] = title
+			plugin.conf['opensearch.xml'] = xml
+		}
+	end
+
+	describe "in day mode" do
+		before do
+			plugin = setup_opensearch_ad_plugin('OpenSearch', 'http://example.com/opensearch.xml', 'day')
+			@header_snippet = plugin.header_proc
+		end
+
+		it { @header_snippet.should == expected_link_tag_with(
+				:title => 'OpenSearch',
+				:xml => 'http://example.com/opensearch.xml')}
+	end
+
+	describe "in latest mode" do
+		before do
+			plugin = setup_opensearch_ad_plugin('OpenSearch', 'http://example.com/opensearch.xml', 'latest')
+			@header_snippet = plugin.header_proc
+		end
+
+		it { @header_snippet.should == expected_link_tag_with(
+				:title => 'OpenSearch',
+				:xml => 'http://example.com/opensearch.xml')}
+	end
+
+	describe "in edit mode" do
+		before do
+			plugin = setup_opensearch_ad_plugin('OpenSearch', 'http://example.com/opensearch.xml', 'edit')
+			@header_snippet = plugin.header_proc
+		end
+
+		it { @header_snippet.should be_empty }
+	end
+
+	def expected_link_tag_with(options)
+		result = <<-HTML
+		<link type="application/opensearchdescription+xml" rel="search" title="#{options[:title]}" href="#{options[:xml]}">
+   	HTML
+		result.gsub( /^\t/, '' ).chomp
+	end
+end
Index: /tdiary/branches/upstream/contrib/spec/title_anchor_spec.rb
===================================================================
--- /tdiary/branches/upstream/contrib/spec/title_anchor_spec.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/spec/title_anchor_spec.rb (revision 710)
@@ -0,0 +1,39 @@
+$:.unshift(File.dirname(__FILE__))
+require 'spec_helper'
+
+describe "title_anchor plugin" do
+	def setup_title_anchor_plugin(mode)
+		fake_plugin(:title_anchor) { |plugin|
+			plugin.mode = mode
+			plugin.conf.index = ''
+			plugin.conf.html_title = "HsbtDiary"
+		}
+	end
+
+	describe "in day mode" do
+		before do
+			@plugin = setup_title_anchor_plugin('day')
+		end
+
+		it { @plugin.title_anchor.should  == expected_html_title_in_day(
+				:index => '',
+				:html_title => 'HsbtDiary')}
+	end
+
+	describe "in latest mode" do
+		before do
+			@plugin = setup_title_anchor_plugin('latest')
+		end
+
+		it { @plugin.title_anchor.should  == expected_html_title_in_latest(
+				:html_title => 'HsbtDiary')}
+	end
+
+	def expected_html_title_in_day(options)
+		expected = %{<h1><a href="#{options[:index]}">#{options[:html_title]}</a></h1>}
+	end
+
+	def expected_html_title_in_latest(options)
+		expected = %{<h1>#{options[:html_title]}</h1>}
+	end
+end
Index: /tdiary/branches/upstream/contrib/spec/youtube_spec.rb
===================================================================
--- /tdiary/branches/upstream/contrib/spec/youtube_spec.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/spec/youtube_spec.rb (revision 710)
@@ -0,0 +1,30 @@
+$:.unshift(File.dirname(__FILE__))
+require 'spec_helper'
+
+describe "youtube plugin" do
+	DUMMY_YOUTUBE_VIDEO_ID = 1234567890
+
+  with_fixtures :user_agent => :expected do
+    it 'should render object tag in :user_agent' do |user_agent, expected|
+      cgi = CGIFake.new
+      plugin = fake_plugin(:youtube)
+      cgi.user_agent = user_agent
+      plugin.conf.cgi = cgi
+      plugin.youtube(DUMMY_YOUTUBE_VIDEO_ID).should == expected
+    end
+
+    set_fixtures([
+      ['DoCoMo'  => 
+          %|<div class="youtube"><a href="http://www.youtube.com/watch?v=#{DUMMY_YOUTUBE_VIDEO_ID}">YouTube (#{DUMMY_YOUTUBE_VIDEO_ID})</a></div>| 
+      ],
+      ['iPhone'  => 
+          %|<div class="youtube"><a href="youtube:#{DUMMY_YOUTUBE_VIDEO_ID}">YouTube (#{DUMMY_YOUTUBE_VIDEO_ID})</a></div>|
+      ],
+      ["Mozilla" => 
+          %|\t\t<object width="425" height="350"><param name="movie" value="http://www.youtube.com/v/#{DUMMY_YOUTUBE_VIDEO_ID}"></param><embed src="http://www.youtube.com/v/#{DUMMY_YOUTUBE_VIDEO_ID}" type="application/x-shockwave-flash" width="425" height="350"></embed></object>
+|
+      ],
+    ])
+  end
+end
+
Index: /tdiary/branches/upstream/contrib/spec/openid_spec.rb
===================================================================
--- /tdiary/branches/upstream/contrib/spec/openid_spec.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/spec/openid_spec.rb (revision 710)
@@ -0,0 +1,257 @@
+$:.unshift(File.dirname(__FILE__))
+require 'spec_helper'
+
+describe "openid plugin w/" do
+	def setup_open_id_plugin(service, userid)
+		fake_plugin(:openid) { |plugin|
+			plugin.mode = 'latest'
+			plugin.conf['openid.service'] = service
+			plugin.conf['openid.id'] = userid
+		}
+	end
+
+	describe "Hatena" do
+		before do
+			plugin = setup_open_id_plugin('Hatena', 'tdtds')
+			@header_snippet = plugin.header_proc
+		end
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => 'openid.server',
+				:href => 'https://www.hatena.ne.jp/openid/server')}
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => 'openid.delegate',
+				:href => 'http://www.hatena.ne.jp/tdtds/')}
+	end
+
+	describe "livedoor" do
+		before do
+			plugin = setup_open_id_plugin('livedoor', 'tdtds')
+			@header_snippet = plugin.header_proc
+		end
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => 'openid.server',
+				:href => 'http://auth.livedoor.com/openid/server')}
+
+		it { @header_snippet.should include_link_tag_with(
+			:rel => 'openid.delegate',
+			:href => 'http://profile.livedoor.com/tdtds')}
+	end
+
+	describe "LiveJournal" do
+		before do
+			plugin = setup_open_id_plugin('LiveJournal', 'tdtds')
+			@header_snippet = plugin.header_proc
+		end
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => 'openid.server',
+				:href => 'http://www.livejournal.com/openid/server.bml')}
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => 'openid.delegate',
+				:href => 'http://tdtds.livejournal.com/')}
+
+	end
+
+	describe "OpenID.ne.jp" do
+		before do
+			plugin = setup_open_id_plugin('OpenID.ne.jp', 'tdtds')
+			@header_snippet = plugin.header_proc
+		end
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => 'openid.server',
+				:href => 'http://www.openid.ne.jp/user/auth')}
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => 'openid.delegate',
+				:href => 'http://tdtds.openid.ne.jp')}
+
+		it { @header_snippet.should include_xrds_meta_tag_with(
+				:content => 'http://tdtds.openid.ne.jp/user/xrds')}
+
+	end
+
+	describe "TypeKey" do
+		before do
+			plugin = setup_open_id_plugin('TypeKey', 'tdtds')
+			@header_snippet = plugin.header_proc
+		end
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => 'openid.server',
+				:href => 'http://www.typekey.com/t/openid/')}
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => 'openid.delegate',
+				:href => 'http://profile.typekey.com/tdtds/')}
+
+	end
+
+	describe "Videntity.org" do
+		before do
+			plugin = setup_open_id_plugin('Videntity.org', 'tdtds')
+			@header_snippet = plugin.header_proc
+		end
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => 'openid.server',
+				:href => 'http://videntity.org/serverlogin?action=openid')}
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => 'openid.delegate',
+				:href => 'http://tdtds.videntity.org/')}
+
+	end
+
+	describe "Vox" do
+		before do
+			plugin = setup_open_id_plugin('Vox', 'tdtds')
+			@header_snippet = plugin.header_proc
+		end
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => 'openid.server',
+				:href => 'http://www.vox.com/services/openid/server')}
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => 'openid.delegate',
+				:href => 'http://tdtds.vox.com/')}
+	end
+
+	describe "myopenid.com" do
+		before do
+			@plugin = setup_open_id_plugin('myopenid.com', 'tdtds')
+			@header_snippet = @plugin.header_proc
+		end
+
+		it { @header_snippet.should include_xrds_meta_tag_with(
+				:content => "http://www.myopenid.com/xrds?username=tdtds")}
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => "openid.server",
+				:href => "http://www.myopenid.com/server")}
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => "openid.delegate",
+				:href => "http://tdtds.myopenid.com")}
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => "openid2.provider",
+				:href => "http://www.myopenid.com/server")}
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => "openid2.local_id",
+				:href => "http://tdtds.myopenid.com")}
+	end
+
+	describe "claimID.com" do
+		before do
+			@plugin = setup_open_id_plugin('claimID.com', 'tdtds')
+			@header_snippet = @plugin.header_proc
+		end
+
+		it { @header_snippet.should include_xrds_meta_tag_with(
+				:content => "http://claimid.com/tdtds/xrds")}
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => "openid.server",
+				:href => "http://openid.claimid.com/server")}
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => "openid.delegate",
+				:href => "http://openid.claimid.com/tdtds")}
+	end
+
+	describe "Personal Identity Provider (PIP)" do
+		before do
+			@plugin = setup_open_id_plugin('Personal Identity Provider (PIP)', 'tdtds')
+			@header_snippet = @plugin.header_proc
+		end
+
+		it { @header_snippet.should include_xrds_meta_tag_with(
+				:content => "http://pip.verisignlabs.com/user/tdtds/yadisxrds")}
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => "openid.server",
+				:href => "http://pip.verisignlabs.com/server")}
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => "openid.delegate",
+				:href => "http://tdtds.pip.verisignlabs.com/")}
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => "openid2.provider",
+				:href => "http://pip.verisignlabs.com/server")}
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => "openid2.local_id",
+				:href => "http://tdtds.pip.verisignlabs.com/")}
+	end
+
+	describe "Yahoo! Japan" do
+		before do
+			plugin = setup_open_id_plugin('Yahoo! Japan', 'tdtds')
+			@header_snippet = plugin.header_proc
+		end
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => 'openid2.provider',
+				:href => 'https://open.login.yahooapis.jp/openid/op/auth')}
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => 'openid2.local_id',
+				:href => 'https://me.yahoo.co.jp/a/tdtds')}
+
+		it { @header_snippet.should_not include_link_tag_with(
+				:rel => "openid.server")}
+
+		it { @header_snippet.should_not include_link_tag_with(
+				:rel => "openid.delegate")}
+
+	end
+
+	describe "Yahoo!" do
+		before do
+			plugin = setup_open_id_plugin('Yahoo!', 'tdtds')
+			@header_snippet = plugin.header_proc
+		end
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => 'openid2.provider',
+				:href => 'https://open.login.yahooapis.com/openid/op/auth')}
+
+		it { @header_snippet.should include_link_tag_with(
+				:rel => 'openid2.local_id',
+				:href => 'https://me.yahoo.com/a/tdtds')}
+
+		it { @header_snippet.should_not include_link_tag_with(
+				:rel => "openid.server")}
+
+		it { @header_snippet.should_not include_link_tag_with(
+				:rel => "openid.delegate")}
+	end
+
+	def include_link_tag_with(options)
+		msg = "include #{options[:rel]} link tag"
+		expected = %|<link rel="#{options[:rel]}"| if options[:rel]
+		expected <<= %| href="#{options[:href]}">| if options[:href]
+			Spec::Matchers::SimpleMatcher.new(msg) do |actual|
+			actual.include?( expected )
+		end
+	end
+
+	def include_xrds_meta_tag_with(options)
+		msg = "include XRDS meta tag"
+		expected = (<<-"EOS").chomp
+<meta http-equiv="X-XRDS-Location" content="#{options[:content]}">
+      EOS
+		Spec::Matchers::SimpleMatcher.new(msg) do |actual|
+				actual.include?( expected )
+		end
+	end
+
+end
Index: /tdiary/branches/upstream/contrib/spec/twitter_js_spec.rb
===================================================================
--- /tdiary/branches/upstream/contrib/spec/twitter_js_spec.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/spec/twitter_js_spec.rb (revision 710)
@@ -0,0 +1,116 @@
+$:.unshift(File.dirname(__FILE__))
+require 'spec_helper'
+require 'time'
+
+describe "twitter_js plugin" do
+	def setup_twitter_js_plugin(mode, user_id)
+		fake_plugin(:twitter_js) { |plugin|
+			plugin.mode = mode
+			plugin.conf['twitter.user'] = user_id
+			plugin.date = Time.parse("20080124")
+		}
+	end
+
+	describe "should render javascript and div tag in day" do
+		before do
+			@plugin = setup_twitter_js_plugin("day", "123456789")
+		end
+		
+		it "for header" do
+			snippet = @plugin.header_proc
+			snippet.should == expected_html_header_snippet("123456789")
+		end
+		
+		it "for body leave" do
+			snippet = @plugin.body_leave_proc(Time.parse("20080124"))
+			snippet.should == expected_html_body_snippet
+		end
+	end
+
+	describe "should render javascript and div tag in latest" do
+		before do
+			@plugin = setup_twitter_js_plugin("latest", "123456789")
+		end
+		
+		it "for header" do
+			snippet = @plugin.header_proc
+			snippet.should == expected_html_header_snippet("123456789")
+		end
+		
+		it "for body leave" do
+			snippet = @plugin.body_leave_proc(Time.parse("20080124"))
+			snippet.should == expected_html_body_snippet
+		end
+	end
+
+	describe "should not render in edit" do
+		before do
+			@plugin = setup_twitter_js_plugin("edit", "123456789")
+		end
+		
+		it "for header" do
+			snippet = @plugin.header_proc
+			snippet.should be_empty
+		end
+		
+		it "for body leave" do
+			snippet = @plugin.body_leave_proc(Time.parse("20080124"))
+			snippet.should be_empty
+		end
+	end
+
+	describe "should not render when user_id is empty" do
+		before do
+			@plugin = setup_twitter_js_plugin("edit", "")
+		end
+		
+		it "for header" do
+			snippet = @plugin.header_proc
+			snippet.should be_empty
+		end
+		
+		it "for body leave" do
+			snippet = @plugin.body_leave_proc(Time.parse(""))
+			snippet.should be_empty
+		end
+	end
+
+	def expected_html_header_snippet(user_id)
+		expected = <<-EXPECTED
+		<script type="text/javascript"><!--
+		function twitter_cb(a){
+			var f=function(n){return (n<10?"0":"")+n};
+			for(var i=0;i<a.length;i++){
+				var d=new Date(a[i]['created_at'].replace('+0000','UTC'));
+				var id="twitter_statuses_"+f(d.getFullYear())+f(d.getMonth()+1)+f(d.getDate());
+				var e=document.getElementById(id);
+				if(!e) continue;
+				if(!e.innerHTML) e.innerHTML='<h3><a href="http://twitter.com/#{user_id}">Twitter statuses</a></h3>';
+				e.innerHTML+='<p><strong>'+a[i]['text']+'</strong> ('+f(d.getHours())+':'+f(d.getMinutes())+':'+f(d.getSeconds())+')</p>';
+			}
+		}
+		function twitter_js(){
+			var e=document.createElement("script");
+			e.type="text/javascript";
+			e.src="http://twitter.com/statuses/user_timeline/#{user_id}.json?callback=twitter_cb&amp;count=20";
+			document.documentElement.appendChild(e);
+		}
+		if(window.addEventListener){
+			window.addEventListener('load',twitter_js,false);
+		}else if(window.attachEvent){
+			window.attachEvent('onload',twitter_js);
+		}else{
+			window.onload=twitter_js;
+		}
+		// --></script>
+		EXPECTED
+		expected.gsub(/^\t/, '').chomp
+	end
+
+	def expected_html_body_snippet
+		expected = <<-HTML
+		<div id="twitter_statuses_20080124" class="section"></div>
+		HTML
+		expected.gsub( /^\t/, '' ).chomp
+	end
+end
Index: /tdiary/branches/upstream/contrib/spec/jmonth_spec.rb
===================================================================
--- /tdiary/branches/upstream/contrib/spec/jmonth_spec.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/spec/jmonth_spec.rb (revision 710)
@@ -0,0 +1,36 @@
+$:.unshift(File.dirname(__FILE__))
+require 'spec_helper'
+require 'time'
+
+describe "jmonth plugin" do
+  with_fixtures :date => :jmonth do
+    def setup_jmonth_plugin(date)
+      fake_plugin(:jmonth) { |plugin|
+        plugin.date = date
+      }
+    end
+
+    filters({
+      :date => lambda {|val| Time.parse(val) }
+    })
+
+    it 'in :jmonth' do |date, jmonth|
+      setup_jmonth_plugin(date).date.strftime('%i').should == jmonth
+    end
+
+    set_fixtures([
+      [ '2007/01/01' => '睦月'] ,
+      [ '2007/02/01' => '如月'] ,
+      [ '2007/03/01' => '弥生'] ,
+      [ '2007/04/01' => '卯月'] ,
+      [ '2007/05/01' => '皐月'] ,
+      [ '2007/06/01' => '水無月' ] ,
+      [ '2007/07/01' => '文月'] ,
+      [ '2007/08/01' => '葉月'] ,
+      [ '2007/09/01' => '長月'] ,
+      [ '2007/10/01' => '神無月'],
+      [ '2007/11/01' => '霜月'] ,
+      [ '2007/12/01' => '師走'] ,
+    ]) 
+  end
+end
Index: /tdiary/branches/upstream/contrib/spec/spec_helper.rb
===================================================================
--- /tdiary/branches/upstream/contrib/spec/spec_helper.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/spec/spec_helper.rb (revision 710)
@@ -0,0 +1,193 @@
+$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "plugin")))
+require 'rubygems'
+require 'spec'
+require 'spec/fixture'
+require 'erb'
+
+# FIXME PluginFake in under construction.
+class PluginFake
+	include ERB::Util
+
+	attr_reader :conf
+	attr_accessor :mode, :date
+
+	def initialize
+		@conf = Config.new
+		@mode = ""
+		@date = nil
+		@header_procs = []
+		@footer_procs = []
+		@update_procs = []
+		@body_enter_procs = []
+		@body_leave_procs = []
+	end
+
+	def add_conf_proc( key, label, genre=nil, &block )
+		# XXX Do we need to verify add_* called??
+	end
+
+	def add_header_proc( block = Proc::new )
+		@header_procs << block
+	end
+
+	def add_footer_proc( block = Proc::new )
+		@footer_procs << block
+	end
+
+	def add_update_proc( block = Proc::new )
+		@update_procs << block
+	end
+
+	def header_proc
+		r = []
+		@header_procs.each do |proc|
+			r << proc.call
+		end
+		r.join.chomp
+	end
+	
+	def footer_proc
+		r = []
+		@footer_procs.each do |proc|
+			r << proc.call
+		end
+		r.join.chomp
+	end
+
+	def add_body_enter_proc( block = Proc::new )
+		@body_enter_procs << block
+	end
+	
+	def body_enter_proc( date )
+		r = []
+		@body_enter_procs.each do |proc|
+			r << proc.call( date )
+		end
+		r.join.chomp
+	end
+
+	def add_body_leave_proc( block = Proc::new )
+		@body_leave_procs << block
+	end
+	
+	def body_leave_proc( date )
+		r = []
+		@body_leave_procs.each do |proc|
+			r << proc.call( date )
+		end
+		r.join.chomp
+	end
+
+	class Config
+
+		attr_accessor :index, :html_title, :cgi
+
+		def initialize
+			@cgi = CGIFake.new
+			@options = {}
+			@options2 = {}
+			@index = './'
+			@html_title = ''
+
+			bot = ["bot", "spider", "antenna", "crawler", "moget", "slurp"]
+			bot += @options['bot'] || []
+			@bot = Regexp::new( "(#{bot.uniq.join( '|' )})", true )
+		end
+
+		def []( key )
+			@options[key]
+		end
+
+		def []=( key, val )
+			@options2[key] = @options[key] = val
+		end
+
+		def delete( key )
+			@options.delete( key )
+			@options2.delete( key )
+		end
+
+		def base_url
+			begin
+				if @options['base_url'].length > 0 then
+					return @options['base_url']
+				end
+			rescue
+			end
+		end
+
+		def mobile_agent?
+			@cgi.mobile_agent?
+		end
+
+		def bot?
+			@bot =~ @cgi.user_agent
+		end
+	end
+
+	def iphone?
+		@conf.cgi.iphone?
+	end
+	alias ipod? iphone?
+end
+
+class CGIFake
+	attr_accessor :user_agent
+
+	def initialize
+		@user_agent = ""
+	end
+
+	def mobile_agent?
+		self.user_agent =~ %r[
+			^DoCoMo|
+			^(?:KDDI|UP\.Browser)|
+			^(?:J-(?:PHONE|EMULATOR)|Vodafone|SoftBank|MOT-|[VS]emulator)|
+			WILLCOM|DDIPOCKET|
+			PDXGW|ASTEL|Palmscape|Xiino|sharp\ pda\ browser|Windows\ CE|L-mode
+		]x
+	end
+
+	def iphone?
+		self.user_agent =~ /iP(?:hone|od)/
+	end
+end
+
+
+def fake_plugin( name_sym, cgi=nil, base=nil, &block )
+	plugin = PluginFake.new
+	yield plugin if block_given?
+
+	file_path = plugin_path( name_sym, base )
+	plugin_name = File.basename( file_path, ".rb" )
+
+	plugin.instance_eval do
+		eval( File.read( file_path ), binding,
+			"(#{File.basename(file_path)})", 1 )
+	end
+	plugin_sym = plugin_name.to_sym
+	if plugin.class.private_method_defined?( plugin_sym )
+		plugin.__send__( :public, plugin_sym )
+	end
+
+	plugin
+end
+
+def plugin_path( plugin_sym, base=nil )
+	paths = []
+	paths << ( base ? base : "plugin" )
+	paths << "#{plugin_sym.to_s}.rb"
+	File.expand_path( File.join( paths ))
+end
+
+def anchor( s )
+	if /^([\-\d]+)#?([pct]\d*)?$/ =~ s then
+		if $2 then
+			"?date=#$1##$2"
+		else
+			"?date=#$1"
+		end
+	else
+		""
+	end
+end
Index: /tdiary/branches/upstream/contrib/spec/account_ad_spec.rb
===================================================================
--- /tdiary/branches/upstream/contrib/spec/account_ad_spec.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/spec/account_ad_spec.rb (revision 710)
@@ -0,0 +1,74 @@
+$:.unshift(File.dirname(__FILE__))
+require 'spec_helper'
+require 'time'
+
+describe "account_ad plugin" do
+	def setup_account_ad_plugin(service, name, mode)
+		fake_plugin(:account_ad) { |plugin|
+			plugin.mode = mode
+			plugin.conf['account.service'] = service
+			plugin.conf['account.name'] = name
+			plugin.conf['base_url'] = 'http://www.hsbt.org/diary/'
+			plugin.date = Time.parse("20070120")
+		}
+	end
+
+	describe "Hatena" do
+		describe "in day mode" do
+			before do
+				plugin = setup_account_ad_plugin('Hatena', 'hsbt', 'day')
+				@header_snippet = plugin.header_proc
+			end
+			
+			it { @header_snippet.should include_description_about_with(
+					:permalink => 'http://www.hsbt.org/diary/?date=20070120')}
+			
+			it { @header_snippet.should include_account_service_with(
+					:service => 'http://www.hatena.ne.jp/')}
+			
+			it { @header_snippet.should include_account_name_with(
+					:name => 'hsbt')}
+		end
+
+		describe "in latest mode" do
+			before do
+				plugin = setup_account_ad_plugin('Hatena', 'hsbt', 'latest')
+				@header_snippet = plugin.header_proc
+			end
+			
+			it { @header_snippet.should include_description_about_with(
+					:permalink => 'http://www.hsbt.org/diary/')}
+			
+			it { @header_snippet.should include_account_service_with(
+					:service => 'http://www.hatena.ne.jp/')}
+			
+			it { @header_snippet.should include_account_name_with(
+					:name => 'hsbt')}
+		end
+	end
+
+	def include_description_about_with(options)
+		msg = "include #{options[:permalink]}"
+		expected = %|<rdf:Description rdf:about="#{options[:permalink]}">|
+			Spec::Matchers::SimpleMatcher.new(msg) do |actual|
+			actual.include?(expected)
+		end
+	end
+
+	def include_account_service_with(options)
+		msg = "include #{options[:service]}"
+		expected = %|<foaf:accountServiceHomepage rdf:resource="#{options[:service]}" />|
+			Spec::Matchers::SimpleMatcher.new(msg) do |actual|
+			actual.include?(expected)
+		end
+	end
+
+	def include_account_name_with(options)
+		msg = "include #{options[:name]}"
+		expected = %|<foaf:OnlineAccount foaf:accountName="#{options[:name]}">|
+			Spec::Matchers::SimpleMatcher.new(msg) do |actual|
+			actual.include?(expected)
+		end
+	end
+
+end
Index: /tdiary/branches/upstream/contrib/spec/my_hotentry_spec.rb
===================================================================
--- /tdiary/branches/upstream/contrib/spec/my_hotentry_spec.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/spec/my_hotentry_spec.rb (revision 710)
@@ -0,0 +1,77 @@
+$:.unshift(File.dirname(__FILE__))
+require 'spec_helper'
+require 'tmpdir'
+require 'fileutils'
+
+
+describe "MyHotEntry" do
+	def cache_filename
+		"#{File.basename(__FILE__, ".rb")}-#{$$}"
+	end
+	before(:each) do
+		fake_plugin(:my_hotentry)
+		@cache_path = File.join(Dir.tmpdir, cache_filename)
+		Dir.mkdir(@cache_path)
+		@dbfile = "#{@cache_path}/my_hotentry.dat"
+		@base_url = 'http://d.hatena.ne.jp/'
+		@hotentry = MyHotEntry.new(@dbfile)
+	end
+
+	after(:each) do
+		FileUtils.rmtree(@cache_path)
+	end
+
+	describe "#update" do
+		before do
+			@hotentry.update(@base_url)
+			@entries = @hotentry.entries
+		end
+
+		it "キャッシュファイルが生成されていること" do
+			File.should be_file(@dbfile)
+		end
+
+		it "人気の日記が取得できていること" do
+			@entries.size.should > 0
+		end
+
+		it "取得したエントリにbase_urlとタイトルが含まれていること" do
+			@entries.each do |entry|
+				entry[:url].should be_include(@base_url)
+				entry[:title].size.should > 0
+			end
+		end
+	end
+
+	describe "何度もupdateした場合" do
+		before do
+			@hotentry.update(@base_url)
+			@original_entry_size = @hotentry.entries.size
+			@hotentry.update(@base_url)
+			@entry_size = @hotentry.entries.size
+		end
+
+		it "キャッシュサイズが大きくならないこと" do
+			@entry_size.should == @original_entry_size
+		end
+	end
+
+	describe "取得結果が空の場合" do
+		before do
+			@exist_url = 'http://d.hatena.ne.jp/'
+			@empty_url = 'http://empty-url.example.com/'
+		end
+
+		it "キャッシュをクリアしないこと" do
+			@hotentry.update(@empty_url)
+			@hotentry.entries.size.should == 0
+
+			@hotentry.update(@exist_url)
+			@hotentry.entries.size.should > 0
+			exist_size = @hotentry.entries.size
+
+			@hotentry.update(@empty_url)
+			@hotentry.entries.size.should == exist_size
+		end
+	end
+end
Index: /tdiary/branches/upstream/contrib/spec/rcov.opts
===================================================================
--- /tdiary/branches/upstream/contrib/spec/rcov.opts (revision 710)
+++ /tdiary/branches/upstream/contrib/spec/rcov.opts (revision 710)
@@ -0,0 +1,2 @@
+-x /var/lib/gem
+-x spec/spec_helper.rb
Index: /tdiary/branches/upstream/contrib/spec/google_video_spec.rb
===================================================================
--- /tdiary/branches/upstream/contrib/spec/google_video_spec.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/spec/google_video_spec.rb (revision 710)
@@ -0,0 +1,28 @@
+$:.unshift(File.dirname(__FILE__))
+require 'spec_helper'
+
+describe "google_video plugin" do
+	DUMMY_VIDEO_ID = 1234567890
+
+  with_fixtures [:width, :height] => :expected do
+    it 'should render :width x :height object tag' do |input, expected|
+      plugin = fake_plugin(:google_video)
+      snippet = plugin.google_video(DUMMY_VIDEO_ID, "#{input[:width]}x#{input[:height]}")
+      snippet.should == expected
+    end
+
+    filters({
+      :expected => lambda {|val|
+        width, height = *val
+        doc_id = DUMMY_VIDEO_ID
+
+       %|<object class="googlevideo" width="#{width}" height="#{height}"><param name="movie" value="http://video.google.com/googleplayer.swf?docId=#{doc_id}&hl=en"></param><embed src="http://video.google.com/googleplayer.swf?docId=#{doc_id}&hl=en" type="application/x-shockwave-flash" width="#{width}" height="#{height}"></embed></object>|
+      },
+    })
+
+    set_fixtures([
+      [ [212, 160] => [212, 160] ],
+      [ [425, 320] => [425, 320] ],
+    ])
+  end
+end
Index: /tdiary/branches/upstream/contrib/spec/spec.opts
===================================================================
--- /tdiary/branches/upstream/contrib/spec/spec.opts (revision 710)
+++ /tdiary/branches/upstream/contrib/spec/spec.opts (revision 710)
@@ -0,0 +1,2 @@
+--loadby mtime
+--reverse
Index: /tdiary/branches/upstream/contrib/misc/section_footer2/yahoo.yaml
===================================================================
--- /tdiary/branches/upstream/contrib/misc/section_footer2/yahoo.yaml (revision 710)
+++ /tdiary/branches/upstream/contrib/misc/section_footer2/yahoo.yaml (revision 710)
@@ -0,0 +1,6 @@
+url: http://bookmarks.yahoo.co.jp/url?url=
+src: http://i.yimg.jp/images/sicons/ybm16.gif
+title:
+  ja: このエントリを含む Yahoo!ブックマーク
+  =: this entry on Yahoo! JAPAN Bookmarks
+counter: http://num.bookmarks.yahoo.co.jp/image/small/
Index: /tdiary/branches/upstream/contrib/misc/section_footer2/hatena.yaml
===================================================================
--- /tdiary/branches/upstream/contrib/misc/section_footer2/hatena.yaml (revision 710)
+++ /tdiary/branches/upstream/contrib/misc/section_footer2/hatena.yaml (revision 710)
@@ -0,0 +1,6 @@
+url: http://b.hatena.ne.jp/entry/
+src: http://d.hatena.ne.jp/images/b_entry.gif
+title:
+  ja: このエントリを含むはてなブックマーク
+  =: this entry on Hatena Bookmark
+counter: http://b.hatena.ne.jp/entry/image/normal/
Index: /tdiary/branches/upstream/contrib/misc/section_footer2/ldc.yaml
===================================================================
--- /tdiary/branches/upstream/contrib/misc/section_footer2/ldc.yaml (revision 710)
+++ /tdiary/branches/upstream/contrib/misc/section_footer2/ldc.yaml (revision 710)
@@ -0,0 +1,6 @@
+url: http://clip.livedoor.com/page/
+src: http://parts.blog.livedoor.jp/img/cmn/clip_16_16_w.gif
+title:
+  ja: このエントリを含む livedoor クリップ
+  =: this entry on livedoor clip
+counter: http://image.clip.livedoor.com/counter/
Index: /tdiary/branches/upstream/contrib/misc/section_footer2/fc2.yaml
===================================================================
--- /tdiary/branches/upstream/contrib/misc/section_footer2/fc2.yaml (revision 710)
+++ /tdiary/branches/upstream/contrib/misc/section_footer2/fc2.yaml (revision 710)
@@ -0,0 +1,6 @@
+url: http://bookmark.fc2.com/search/detail?url=
+src: http://bookmark.fc2.com/img/add-16.gif
+title:
+  ja: このエントリを含む FC2ブックマーク
+  =: this entry on FC2 Bookmark
+counter: http://bookmark.fc2.com/image/users/
Index: /tdiary/branches/upstream/contrib/misc/section_footer2/buzzurl.yaml
===================================================================
--- /tdiary/branches/upstream/contrib/misc/section_footer2/buzzurl.yaml (revision 710)
+++ /tdiary/branches/upstream/contrib/misc/section_footer2/buzzurl.yaml (revision 710)
@@ -0,0 +1,6 @@
+url: http://buzzurl.jp/entry/
+src: http://buzzurl.jp/static/image/api/icon/add_icon_mini_10.gif
+title:
+  ja: このエントリを含む Buzzurl
+  =:  this entry on Buzzurl
+counter: http://buzzurl.jp/api/counter/
Index: /tdiary/branches/upstream/contrib/util/clean-spam/tdiary-referer-clean
===================================================================
--- /tdiary/branches/upstream/contrib/util/clean-spam/tdiary-referer-clean (revision 710)
+++ /tdiary/branches/upstream/contrib/util/clean-spam/tdiary-referer-clean (revision 710)
@@ -0,0 +1,46 @@
+#!/usr/bin/env ruby
+#
+# Copyright (C) 2004 Satoru Takabayashi <satoru@namazu.org>
+# You can redistribute it and/or modify it under GPL2.
+#
+puts "Usage: tdiary-referer-clean PATTERN FILE..." if ARGV.length == 0
+pattern = Regexp.new(ARGV.shift)
+file_names = ARGV
+
+deleted_referers = []
+file_names.each {|file_name|
+  tmp_name = "tmp.#{Process.pid}"
+  i = File.open(file_name)
+  o = File.open(tmp_name, "w")
+
+  first_line = i.gets
+  o.print first_line
+
+  while true
+    date_line = i.gets
+    break if date_line.nil?
+    raise unless /^Date: /.match(date_line)
+    blank_line = i.gets
+    raise unless blank_line == "\n"
+
+    o.print date_line
+    o.print blank_line
+    while line = i.gets
+      if line == ".\n"
+        o.print line
+        next
+      end
+      if pattern.match(line)
+        deleted_referers.push(line)
+      else
+        o.print line
+      end
+    end
+  end
+  i.close
+  o.close
+
+  File.rename(file_name, file_name + ".bak")
+  File.rename(tmp_name, file_name)
+}
+deleted_referers.each {|referer| print referer }
Index: /tdiary/branches/upstream/contrib/util/clean-spam/README.ja
===================================================================
--- /tdiary/branches/upstream/contrib/util/clean-spam/README.ja (revision 710)
+++ /tdiary/branches/upstream/contrib/util/clean-spam/README.ja (revision 710)
@@ -0,0 +1,1 @@
+See http://www.namazu.org/~satoru/diary/20040923.html#p01
Index: /tdiary/branches/upstream/contrib/util/clean-spam/tdiary-comment-clean
===================================================================
--- /tdiary/branches/upstream/contrib/util/clean-spam/tdiary-comment-clean (revision 710)
+++ /tdiary/branches/upstream/contrib/util/clean-spam/tdiary-comment-clean (revision 710)
@@ -0,0 +1,46 @@
+#!/usr/bin/env ruby
+#
+# Copyright (C) 2004 Satoru Takabayashi <satoru@namazu.org>
+# You can redistribute it and/or modify it under GPL2.
+#
+puts "Usage: tdiary-comment-clean PATTERN FILE..." if ARGV.length == 0
+pattern = Regexp.new(ARGV.shift)
+file_names = ARGV
+
+deleted_comments = []
+file_names.each {|file_name|
+  i = File.open(file_name)
+  first_line = i.gets
+
+  comments = []
+  comment = ""
+  while line = i.gets
+    if line == ".\n"
+      comments.push(comment)
+      comment = ""
+    else
+      comment << line
+    end
+  end
+  i.close
+
+  tmp_name = "tmp.#{Process.pid}"
+  File.open(tmp_name, "w") {|o|
+    o.print first_line
+    comments.each {|comment|
+      if pattern.match(comment)
+        deleted_comments.push(comment)
+      else
+        o.print comment
+        o.puts "."
+      end
+    }
+  }
+  File.rename(file_name, file_name + ".bak")
+  File.rename(tmp_name, file_name)
+}
+
+deleted_comments.each {|comment|
+  print comment
+  puts "."
+}
Index: /tdiary/branches/upstream/contrib/util/clean-spam/tdiary-comment-clean2
===================================================================
--- /tdiary/branches/upstream/contrib/util/clean-spam/tdiary-comment-clean2 (revision 710)
+++ /tdiary/branches/upstream/contrib/util/clean-spam/tdiary-comment-clean2 (revision 710)
@@ -0,0 +1,81 @@
+#!/usr/bin/env ruby
+#
+# Copyright (C) 2004 Satoru Takabayashi <satoru@namazu.org>
+# You can redistribute it and/or modify it under GPL2.
+#
+# Modified by machu ( http://www.machu.jp/diary/ )
+# 2008-03-07: adding last-modified filter with -a and -b option
+#
+require 'optparse'
+after_date = Time.at(0)
+before_date = Time.now
+test = false
+
+opt = OptionParser.new
+opt.on('-a AFTER_HOUR') {|v|
+  after_date = Time.at(Time.now - 60 * 60 * v.to_i)
+}
+opt.on('-b BEFORE_HOUR') {|v|
+  before_date = Time.at(Time.now - 60 * 60 * v.to_i)
+}
+opt.on('-t') {|v|
+  test = true
+}
+opt.parse!(ARGV)
+
+puts "Usage: tdiary-comment-clean [-a AFTER_HOUR] [-b BEFORE_HOUR] PATTERN FILE..." if ARGV.length == 0
+pattern = Regexp.new(ARGV.shift)
+file_names = ARGV
+
+class Comment
+  attr_accessor :body, :date
+
+  def initialize
+    @body = ""
+  end
+
+  def <<(body)
+    if body.match(/^Last-Modified: (\d+)$/)
+      @date = Time.at($1.to_i)
+    end
+    @body << body
+  end
+end
+
+deleted_comments = []
+file_names.each {|file_name|
+  i = File.open(file_name)
+  first_line = i.gets
+
+  comments = []
+  comment = Comment.new
+  while line = i.gets
+    if line == ".\n"
+      comments.push(comment)
+      comment = Comment.new
+    else
+      comment << line
+    end
+  end
+  i.close
+
+  tmp_name = "tmp.#{Process.pid}"
+  File.open(tmp_name, "w") {|o|
+    o.print first_line
+    comments.each {|comment|
+      if pattern.match(comment.body) and (before_date > comment.date) and (after_date < comment.date)
+        deleted_comments.push(comment)
+      else
+        o.print comment.body
+        o.puts "."
+      end
+    }
+  }
+  File.rename(file_name, file_name + ".bak") unless test
+  File.rename(tmp_name, file_name) unless test
+}
+
+deleted_comments.each {|comment|
+  print comment.body
+  puts "."
+}
Index: /tdiary/branches/upstream/contrib/util/estraier-search/estraier.rxml
===================================================================
--- /tdiary/branches/upstream/contrib/util/estraier-search/estraier.rxml (revision 710)
+++ /tdiary/branches/upstream/contrib/util/estraier-search/estraier.rxml (revision 710)
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="<%= @conf.encoding %>"?>
+<rss xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/" version="2.0">
+  <channel>
+    <title><%= _( @conf.html_title ) %> [全文検索]: <%= _(@query) %></title>
+    <link><%= @conf.base_url.sub(%r|/[^/]*$|, '/') %><%= _(@cgi.script_name ? File.basename(@cgi.script_name) : "") %><%= format_anchor(@start, @num) %></link>
+    <description>HyperEstraier 検索: <%= _(@query) %></description>
+    <language>ja-JP</language>
+    <openSearch:totalResults><%= @result.doc_num %></openSearch:totalResults>
+    <openSearch:startIndex><%= @start + 1 %></openSearch:startIndex>
+    <openSearch:itemsPerPage><%= @num %></openSearch:itemsPerPage>
+    <% for i in @start...@start+@num %>
+    <% item = @result.get_doc(i) || next %>
+    <% format_result_item(item) %>
+    <item>
+      <title><%= _(@date.sub(/^(\d{4})(\d{2})(\d{2}).*/, "\\1-\\2-\\3")) %> <%= _(@conf.to_native(@title)) %></title>
+      <link><%= @conf.base_url %><%= @plugin.anchor(@date) %></link>
+      <pubDate><%= _(CGI.rfc1123_date(Time.parse(@last_modified))) %></pubDate>
+      <description><%= _(@conf.to_native(@summary)) %></description>
+      <guid><%= @conf.base_url %><%= @plugin.anchor(@date) %></guid>
+    </item>
+    <% end %>
+  </channel>
+</rss>
Index: /tdiary/branches/upstream/contrib/util/estraier-search/en/estraier-register.rb
===================================================================
--- /tdiary/branches/upstream/contrib/util/estraier-search/en/estraier-register.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/util/estraier-search/en/estraier-register.rb (revision 710)
@@ -0,0 +1,3 @@
+@estraier_register_conf_label = 'Estraier Search'
+@estraier_register_conf_header = 'Rebuild Estraier search index'
+@estraier_register_conf_description = 'To rebuild Estraier search index, check the box and submit \'OK\'.'
Index: /tdiary/branches/upstream/contrib/util/estraier-search/estraier-register.rb
===================================================================
--- /tdiary/branches/upstream/contrib/util/estraier-search/estraier-register.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/util/estraier-search/estraier-register.rb (revision 710)
@@ -0,0 +1,293 @@
+#!/usr/bin/env ruby
+# estraier-register.rb $Revision: 1.1.2.13 $
+#
+# Copyright (C) 2007 Kazuhiko <kazuhiko@fdiary.net>
+# You can redistribute it and/or modify it under GPL2.
+#
+require "estraierpure"
+
+mode = ""
+if $0 == __FILE__
+	require 'cgi'
+	ARGV << '' # dummy argument against cgi.rb offline mode.
+	@cgi = CGI::new
+	mode = "CMD"
+else
+	mode = "PLUGIN"
+end
+
+if mode == "CMD"
+	tdiary_path = "."
+	tdiary_conf = "."
+	$stdout.sync = true
+
+	def usage
+		puts "hyper-estraier-register.rb $Revision: 1.1.2.13 $"
+		puts " register to hyper-estraier index files from tDiary's database."
+		puts " usage: ruby hyper-estraier-regiser.rb [-p <tDiary directory>] [-c <tdiary.conf directory>]"
+		exit
+	end
+
+	require 'getoptlong'
+	parser = GetoptLong::new
+	parser.set_options(['--path', '-p', GetoptLong::REQUIRED_ARGUMENT], ['--conf', '-c', GetoptLong::REQUIRED_ARGUMENT])
+	begin
+		parser.each do |opt, arg|
+			case opt
+			when '--path'
+				tdiary_path = arg
+			when '--conf'
+				tdiary_conf = arg
+			end
+		end
+	rescue
+		usage
+		exit( 1 )
+	end
+
+	tdiary_conf = tdiary_path unless tdiary_conf
+	Dir::chdir( tdiary_conf )
+
+	begin
+		$:.unshift tdiary_path
+		require "#{tdiary_path}/tdiary"
+	rescue LoadError
+		$stderr.puts "hyper-estraier-register.rb: cannot load tdiary.rb. <#{tdiary_path}/tdiary>\n"
+		$stderr.puts " usage: ruby hyper-estraier-regiser.rb [-p <tDiary directory>] [-c <tdiary.conf directory>]"
+		exit( 1 )
+	end
+end
+
+module TDiary
+	#
+	# Database
+	#
+	class EstraierDB
+		attr_accessor :db
+		attr_reader :conf
+
+		def initialize(conf)
+			@conf = conf
+			@host = @conf["estraier.host"] || "localhost"
+			@port = @conf["estraier.port"] || 1978
+			@path = @conf["estraier.path"] || "/node/"
+			@node = @conf["estraier.node"] || "tdiary"
+			@name = @conf["estraier.name"] || "admin"
+			@password = @conf["estraier.password"] || "admin"
+		end
+
+		def transaction
+			db = EstraierPure::Node.new
+			db.set_url("http://#{@host}:#{@port}#{@path}#{@node}")
+			db.set_auth(@name, @password)
+
+			if db.doc_num < 0
+				raise "Database not found : http://#{@host}:#{@port}#{@path}#{@node}"
+			end
+			@db = db
+			yield self
+		end
+
+		def cache_path
+			@conf.cache_path || "#{@conf.data_path}cache"
+		end
+	end
+
+	#
+	# Register
+	#
+	class EstraierRegister < TDiaryBase
+		def initialize(estraier_db, diary)
+			@db = estraier_db.db
+			super(CGI::new, 'day.rhtml', estraier_db.conf)
+			@diary = diary
+			@date = diary.date
+			@diaries = {@date.strftime('%Y%m%d') => @diary} if @diaries.empty?
+			@plugin = ::TDiary::Plugin::new(
+							'conf' => @conf,
+							'cgi' => @cgi,
+							'cache_path' => cache_path,
+							'diaries' => @diaries
+							)
+			def @plugin.apply_plugin_alt( str, remove_tag = false )
+				apply_plugin( str, remove_tag )
+			end
+		end
+
+		def execute(force = false)
+			date = @date.strftime('%Y%m%d')
+			cond = EstraierPure::Condition.new
+			if @conf["estraier.with_user_name"]
+				cond.add_attr("@uri STRBW #{@conf.user_name}:#{date}")
+			else
+				cond.add_attr("@uri STRBW #{date}")
+			end
+			result = @db.search(cond, 0)
+			if result
+				for i in 0...result.doc_num
+					doc_id = result.get_doc(i).attr("@id")
+					@db.out_doc(doc_id)
+				end
+			end
+			return unless @diary.visible?
+
+			# body
+			index = 0
+			anchor = ''
+			@diary.each_section do |section|
+				index += 1
+				@conf['apply_plugin'] = true
+				anchor = "#{date}p%02d" % index
+				title = CGI.unescapeHTML( @plugin.apply_plugin_alt( section.subtitle_to_html, true ).strip )
+				if title.empty?
+					title = @plugin.apply_plugin_alt( section.body_to_html, true ).strip
+					title = @conf.shorten( CGI.unescapeHTML( title ), 20 )
+				end
+				last_modified = @diary.last_modified.strftime("%FT%T")
+				body = CGI.unescapeHTML( @plugin.apply_plugin_alt( section.body_to_html, true ).strip )
+				doc = EstraierPure::Document.new
+				doc.add_attr("@title", title)
+				if @conf["estraier.with_user_name"]
+					doc.add_attr("@uri", "#{@conf.user_name}:#{anchor}")
+				else
+					doc.add_attr("@uri", anchor)
+				end
+				doc.add_attr("@mdate", last_modified)
+				doc.add_hidden_text(title)
+				doc.add_text(body)
+				@db.put_doc(doc)
+			end
+
+			# comment
+			@diary.each_visible_comment do |comment, index|
+				if /^(TrackBack|Pingback)$/i =~ comment.name
+					anchor = "#{date}t%02d" % index
+					title = "TrackBack (#{comment.name})"
+				else
+
+
+					anchor = "#{date}c%02d" % index
+					title = "#{@plugin.comment_description_short} (#{comment.name})"
+				end
+				body = comment.body
+				doc = EstraierPure::Document.new
+				doc.add_attr("@title", title)
+				if @conf["estraier.with_user_name"]
+					doc.add_attr("@uri", "#{@conf.user_name}:#{anchor}")
+				else
+					doc.add_attr("@uri", anchor)
+				end
+				doc.add_attr("@mdate", comment.date.strftime("%FT%T"))
+				doc.add_hidden_text(title)
+				doc.add_text(body)
+				@db.put_doc(doc)
+			end
+		end
+		
+		protected
+
+		def mode; 'day'; end
+		def cookie_name; ''; end
+		def cookie_mail; ''; end
+
+		def convert(str)
+			str
+		end
+	end
+
+	#
+	# Main
+	#
+	class EstraierRegisterMain < TDiaryBase
+		def initialize(conf)
+			super(CGI::new, 'day.rhtml', conf)
+		end
+
+		def execute(out = $stdout)
+			require 'fileutils'
+			calendar
+			db = EstraierDB.new(conf)
+			db.transaction do |estraier_db|
+				@years.keys.sort.reverse_each do |year|
+					out << "(#{year.to_s}/) "
+					@years[year.to_s].sort.reverse_each do |month|
+						@io.transaction(Time::local(year.to_i, month.to_i)) do |diaries|
+							diaries.sort.reverse_each do |day, diary|
+								EstraierRegister.new(estraier_db, diary).execute
+								out << diary.date.strftime('%m%d ')
+							end
+							false
+						end
+					end
+				end
+			end
+		end
+	end
+end
+
+if mode == "CMD"
+	begin
+		require 'cgi'
+		if TDiary::Config.instance_method(:initialize).arity > 0
+			# for tDiary 2.1 or later
+			cgi = CGI.new
+			conf = TDiary::Config::new(cgi)
+		else
+			# for tDiary 2.0 or earlier
+			conf = TDiary::Config::new
+		end
+		conf.header = ''
+		conf.footer = ''
+		conf.show_comment = true
+		conf.hide_comment_form = true
+		conf.show_nyear = false
+		def conf.bot?; true; end
+		TDiary::EstraierRegisterMain.new(conf).execute
+	rescue
+		print $!, "\n"
+		$@.each do |v|
+			print v, "\n"
+		end
+		exit( 1 )
+	end
+
+	puts
+else
+	add_update_proc do
+		conf = @conf.clone
+		conf.header = ''
+		conf.footer = ''
+		conf.show_comment = true
+		conf.hide_comment_form = true
+		conf.show_nyear = false
+		def conf.bot?; true; end
+	
+		diary = @diaries[@date.strftime('%Y%m%d')]
+		TDiary::EstraierDB.new(conf).transaction do |estraier_db|
+			TDiary::EstraierRegister.new(estraier_db, diary).execute(true)
+		end
+	end
+
+	if !@conf['estraier.hideconf'] && (@mode == 'conf' || @mode == 'saveconf')
+		args = ['estraier_register', @estraier_register_conf_label]
+		args << 'update' if TDIARY_VERSION > '2.1.3'
+		add_conf_proc(*args) do
+			str = <<-HTML
+<h3 class="subtitle">#{@estraier_register_conf_header}</h3>
+<p>
+<label for="estraier_register_rebuild"><input id="estraier_register_rebuild" type="checkbox" name="estraier_register_rebuild" value="1">
+#{@estraier_register_conf_description}</label>
+</p>
+HTML
+			if @mode == 'saveconf'
+				if @cgi.valid?( 'estraier_register_rebuild' )
+					str << '<p>The following diaries were registered.</p>'
+					out = ''
+					TDiary::EstraierRegisterMain.new(@conf).execute(out)
+					str << "<p>#{out}</p>"
+				end
+			end
+			str
+		end
+	end
+end
Index: /tdiary/branches/upstream/contrib/util/estraier-search/estraier-search.rb
===================================================================
--- /tdiary/branches/upstream/contrib/util/estraier-search/estraier-search.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/util/estraier-search/estraier-search.rb (revision 710)
@@ -0,0 +1,268 @@
+#!/usr/bin/env ruby
+# estraier-search.rb $Revision: 1.1.2.12 $
+#
+# Copyright (C) 2007 Kazuhiko <kazuhiko@fdiary.net>
+# You can redistribute it and/or modify it under GPL2.
+#
+$KCODE= 'u'
+BEGIN { $defout.binmode }
+
+require "estraierpure"
+require "enumerator"
+require "date"
+
+if FileTest::symlink?( __FILE__ ) then
+	org_path = File::dirname( File::readlink( __FILE__ ) )
+else
+	org_path = File::dirname( __FILE__ )
+end
+$:.unshift( org_path.untaint )
+require 'tdiary'
+
+#
+# class TDiaryEstraier
+#
+module TDiary
+	class TDiaryEstraier < ::TDiary::TDiaryBase
+		MAX_PAGES = 20
+		SORT_OPTIONS = [
+			["score", "スコア順"],
+			["date", "日付順"],
+		]
+		ORDER_OPTIONS = [
+			["asc", "昇順"],
+			["desc", "降順"],
+		]
+		FORM_OPTIONS = [
+			["simple", "簡便書式"],
+			["normal", "通常書式"],
+		]
+		NUM_OPTIONS = [10, 20, 30, 50, 100]
+
+		def initialize( cgi, rhtml, conf )
+			super
+			@host = @conf["estraier.host"] || "localhost"
+			@port = @conf["estraier.port"] || 1978
+			@path = @conf["estraier.path"] || "/node/"
+			@node = @conf["estraier.node"] || "tdiary"
+			parse_args
+			format_form
+			if @query.empty?
+				@msg = '検索条件を入力して、「検索」ボタンを押してください'
+			else
+				search
+			end
+		end
+
+		def load_plugins
+			super
+			# add a opensearch rss link
+			@plugin.instance_variable_get('@header_procs').unshift Proc.new {
+				cgi_url = @conf.base_url.sub(%r|/[^/]*$|, '/') + (@cgi.script_name ? _(File.basename(@cgi.script_name)) : '')
+				%Q|\t<link rel="alternate" type="application/rss+xml" title="Search Result RSS" href="#{cgi_url}#{format_anchor(@start, @num)};type=rss">\n|
+			}
+			# override some plugins
+			def @plugin.sn(number = nil); ''; end
+			def @plugin.whats_new; ''; end
+		end
+
+		def eval_rxml
+			require 'time'
+			load_plugins
+			ERB::new( File::open( "#{PATH}/skel/estraier.rxml" ){|f| f.read }.untaint ).result( binding )
+		end
+
+		private
+
+		def parse_args
+			@query = @cgi["query"].strip
+			@start = @cgi["start"].to_i
+			@num = @cgi["num"].to_i
+			if @num < 1
+				@num = 10
+			elsif @num > 100
+				@num = 100
+			end
+			@sort = @cgi["sort"].empty? ? "score" : @cgi["sort"]
+			@order = @cgi["order"].empty? ? "desc" : @cgi["order"]
+			@form = @cgi["form"].empty? ? "simple" : @cgi["form"]
+		end
+
+		def search
+			@db = EstraierPure::Node.new
+			@db.set_url("http://#{@host}:#{@port}#{@path}#{@node}")
+			begin
+				t = Time.now
+				cond = create_search_options
+				cond.set_phrase(convert(@query))
+				@result = @db.search(cond, 0)
+				@secs = Time.now - t
+			rescue
+				@msg = "エラー: #{_($!.to_s + $@.join)}</p>"
+			end
+		end
+
+		def format_result_item(item)
+			@date = item.attr('@uri')
+			if @conf["estraier.with_user_name"]
+				@date.gsub!(/.*:/, "")
+			end
+			@date_str = Date.parse(@date).strftime(@conf.date_format)
+			@last_modified = item.attr('@mdate')
+			@title = _(item.attr('@title'))
+			@summary = _(item.snippet).gsub(/\t.*/, "").gsub(/\n\n/, " ... ").gsub(/\n/, "")
+			for term in @query.split
+				@title.gsub!(Regexp.new(Regexp.quote(CGI.escapeHTML(term)), true, @encoding), "<strong>\\&</strong>")
+				@summary.gsub!(Regexp.new(Regexp.quote(CGI.escapeHTML(term)), true, @encoding), "<strong>\\&</strong>")
+			end
+			query = "[SIMILAR]"
+			item.keywords.split(/\t/).enum_slice(2).collect do |k, s|
+				query << " WITH #{s} #{k}"
+			end
+			@similar = "%s?query=%s" %
+				[_(@cgi.script_name || ""), CGI::escape(query)]
+		end
+
+		def format_links(result)
+			page_count = (result.doc_num - 1) / @num + 1
+			current_page = @start / @num + 1
+			first_page = current_page - (MAX_PAGES / 2 - 1)
+			if first_page < 1
+				first_page = 1
+			end
+			last_page = first_page + MAX_PAGES - 1
+			if last_page > page_count
+				last_page = page_count
+			end
+			buf = "<p class=\"infobar\">\n"
+			if current_page > 1
+				buf.concat(format_link("前へ", @start - @num, @num))
+			end
+			if first_page > 1
+				buf.concat("... ")
+			end
+			for i in first_page..last_page
+				if i == current_page
+					buf.concat("#{i} ")
+				else
+					buf.concat(format_link(i.to_s, (i - 1) * @num, @num))
+				end
+			end
+			if last_page < page_count
+				buf.concat("... ")
+			end
+			if current_page < page_count
+				buf.concat(format_link("次へ", @start + @num, @num))
+			end
+			buf.concat("</p>\n")
+			return buf
+		end
+
+		def format_anchor(start, num)
+			return format('?query=%s;start=%d;num=%d;sort=%s;order=%s', CGI::escape(@query), start, num, _(@sort), _(@order))
+		end
+
+		def format_link(label, start, num)
+			return format('<a href="%s%s">%s</a> ',	_(@cgi.script_name ? @cgi.script_name : ""),  format_anchor(start, num), _(label))
+		end
+
+		def create_search_options
+			cond = EstraierPure::Condition.new
+			if @conf["estraier.with_user_name"]
+				cond.add_attr("@uri STRBW #{@conf.user_name}:")
+			end
+			if @sort == "date"
+				order = "@uri"
+			else
+				order = ""
+			end
+			if @order == "asc"
+				order = "[SCA]" if order.empty?
+			else
+				unless order.empty?
+					order << " STRD"
+				end
+			end
+			if @form == "simple"
+				cond.set_options(EstraierPure::Condition::SIMPLE)
+			end
+			cond.set_order(order)
+			return cond
+		end
+
+		def format_options(options, value)
+			return options.collect { |val, label|
+				if val == value
+					"<option value=\"#{_(val)}\" selected>#{_(label)}</option>"
+				else
+					"<option value=\"#{_(val)}\">#{_(label)}</option>"
+				end
+			}.join("\n")
+		end
+
+		def format_form
+			@num_options = NUM_OPTIONS.collect { |n|
+				if n == @num
+					"<option value=\"#{n}\" selected>#{n}件ずつ</option>"
+				else
+					"<option value=\"#{n}\">#{n}件ずつ</option>"
+				end
+			}.join("\n")
+			@sort_options = format_options(SORT_OPTIONS, @sort)
+			@order_options = format_options(ORDER_OPTIONS, @order)
+			@form_options = format_options(FORM_OPTIONS, @form)
+		end
+
+		def _(str)
+			CGI::escapeHTML(str)
+		end
+
+		def convert(str)
+			@conf.to_native(str)
+		end
+	end
+end
+
+begin
+	@cgi = CGI::new
+	if ::TDiary::Config.instance_method(:initialize).arity > 0
+		# for tDiary 2.1 or later
+		conf = ::TDiary::Config::new(@cgi)
+	else
+		# for tDiary 2.0 or earlier
+		conf = ::TDiary::Config::new
+	end
+	tdiary = TDiary::TDiaryEstraier::new( @cgi, 'estraier.rhtml', conf )
+
+	head = {
+		'type' => 'text/html',
+		'Vary' => 'User-Agent'
+	}
+	if @cgi.mobile_agent? then
+		body = conf.to_mobile( tdiary.eval_rhtml( 'i.' ) )
+		head['charset'] = conf.mobile_encoding
+		head['Content-Length'] = body.size.to_s
+	else
+		if @cgi['type'] == 'rss'
+			head['type'] = "application/xml; charset=#{conf.encoding}"
+			body = tdiary.eval_rxml
+		else
+			body = tdiary.eval_rhtml
+		end
+		head['charset'] = conf.encoding
+		head['Content-Length'] = body.size.to_s
+		head['Pragma'] = 'no-cache'
+		head['Cache-Control'] = 'no-cache'
+	end
+	print @cgi.header( head )
+	print body
+rescue Exception
+	if @cgi then
+		print @cgi.header( 'type' => 'text/plain' )
+	else
+		print "Content-Type: text/plain\n\n"
+	end
+	puts "#$! (#{$!.class})"
+	puts ""
+	puts $@.join( "\n" )
+end
Index: /tdiary/branches/upstream/contrib/util/estraier-search/estraier.rhtml
===================================================================
--- /tdiary/branches/upstream/contrib/util/estraier-search/estraier.rhtml (revision 710)
+++ /tdiary/branches/upstream/contrib/util/estraier-search/estraier.rhtml (revision 710)
@@ -0,0 +1,73 @@
+<div class="adminmenu"><%%=navi_user%></div>
+
+<h1><%= _(@conf.html_title) %> [全文検索]</h1>
+
+<form action="<%= @cgi.script_name ? _(@cgi.script_name) : '' %>">
+<p>
+<input type="text" name="query" value="<%= _(@conf.to_native(@query)) %>" size="60">
+<input type="submit" value="検索">
+<a href="http://hyperestraier.sourceforge.net/uguide-ja.html#searchcond">検索方法</a>
+</p>
+<p>
+並べ替え:
+<select name="sort">
+<%= @sort_options %>
+</select>
+<select name="order">
+<%= @order_options %>
+</select>
+
+表示件数:
+<select name="num">
+<%= @num_options %>
+</select>
+
+検索条件の書式:
+<select name="form">
+<%= @form_options %>
+</select>
+</p>
+</form>
+
+<% if @msg %>
+  <p><%= @msg %></p>
+<% else %>
+  <% @end = [@result.doc_num, @start + @num].min %>
+  <p>
+    <%= _(@conf.to_native(@query)) %> の検索結果
+    <%= @result.doc_num %> 件中 <%= @start + 1 %> - <%= @end %> 件目
+    (<%= @secs %> 秒)
+  </p>
+  <% if @result.doc_num > @num %>
+    <%= format_links(@result) %>
+  <% end %>
+  <% for i in @start...@end %>
+    <% item = @result.get_doc(i) || next %>
+    <% format_result_item(item) %>
+<div class="day">
+<h2><span class="date"><a href="<%= @conf.index %><%= @plugin.anchor(@date) %>"><%= @date_str %></a></span> <span class="nyear">[<a href="<%= @similar %>">類似検索</a>]</span></h2>
+<div class="body">
+<div class="section">
+<h3><a href="<%= @conf.index %><%= @plugin.anchor(@date) %>"><%= @conf.section_anchor %></a><%= @conf.to_native(@title) %></h3>
+<p>
+<%= @conf.to_native(@summary) %>
+</p>
+</div>
+</div>
+<div class="comment">
+<div class="commentshort">
+<p><%= @conf.comment_anchor %>
+<span class="commentator"></span>&nbsp;(スコア:<%= item.attr("#nodescore") %>)</p>
+</div>
+</div>
+<div class="referer">
+</div>
+</div>
+  <% end %>
+  <% if @result.doc_num > @num %>
+    <%= format_links(@result) %>
+  <% end %>
+<% end %>
+
+<div class="footer">
+Powered by <a href="http://hyperestraier.sourceforge.net/">Hyper Estraier</a></div>
Index: /tdiary/branches/upstream/contrib/util/estraier-search/README.ja
===================================================================
--- /tdiary/branches/upstream/contrib/util/estraier-search/README.ja (revision 710)
+++ /tdiary/branches/upstream/contrib/util/estraier-search/README.ja (revision 710)
@@ -0,0 +1,114 @@
+estraier-search README
+===================
+
+  Hyper Estraier <http://hyperestraier.sourceforge.net/> を用いた tDiary
+  検索環境です。
+
+
+特徴
+----------
+
+  日記の更新と連動して自動的にインデックスを更新するので、いつでも最新の
+  情報で検索することができます。日記を HTML 化した後に必要な部分だけを取
+  り出してインデックスを作成するので、ヘッダやフッタなどによる検索ノイズ
+  がなく、また、プラグインの出力が検索対象になるという特徴があります。
+
+
+必要なもの
+----------
+
+  * tDiary 1.5 以降
+  * Ruby 1.8.2 以降
+  * Hyper Estraier 1.4 以降
+    （ピュアRubyインターフェイス estraierpure.rb も必要です）
+
+
+セットアップ
+------------
+
+  1. estraier-register.rb を tDiary の プラグインディレクトリにコピーします。
+
+  2. estraier-search.rb を tDiary の index.rb があるディレクトリにコピー
+     します。必要なら index.rb と同じようにシンボリックリンクを張ったり
+     名前を変えたりしてください。
+
+  3. CGI として実行可能にします。
+
+     $ chmod a+x estraier-search.rb
+
+  4. 必要なら #! のパスを変更します。
+
+  5. estraier.rhtml と estraier.rxml と i.estraier.rhtml を tDiary の
+     skel/ ディレクトリにコピーします。
+
+  6. Hyper Estraier のノードマスタをセットアップします。その際、設定ファ
+     イル（ノードマスタのrootdirの下の_conf）に、以下を追加してください。
+
+       attrindex: @uri{{!}}str
+
+     この設定がないと、登録エントリが増えた際に、著しくパフォーマンスが
+     低下します。
+
+  7. ノードマスタから、tDiary用のノードサーバを作成し、その接続情報を
+     tdiary.conf に記述します。デフォルトでは、以下のように記述したのと
+     同じ設定です。
+
+       @options["estraier.host"] = "localhost"
+       @options["estraier.port"] = 1978
+       @options["estraier.path"] = "/node/"
+       @options["estraier.node"] = "tdiary"
+       @options["estraier.name"] = "admin"
+       @options["estraier.password"] = "admin"
+
+  8. estraier-register.rb プラグインを有効にします。(tDiary の plugin/
+     ディレクトリにコピーするか、プラグイン選択のディレクトリにコピーし
+     てブラウザから有効に設定します。言語リソースファイルの
+     en/estraier-register.rb と ja/estraier-register.rb も、プラグインディ
+     レクトリの en/ 以下およびja/ 以下にコピーしてください。)
+
+  9. 既存の日記コンテンツに対して検索インデックスを作成します。tDiary の
+     設定画面から「Estraier検索」を選び、「Estraier検索のインデックスを
+     再構築する場合は、チェックボックスをチェックしてOKを押してください」
+     というメッセージに従ってチェックしてOKを押すと、
+
+     インデックスの作成は、tDiary の CGI の実行権限で以下のように実行する
+     ことでも可能です。
+
+     ruby estraier-register.rb [-p tdiary.rbのあるディレクトリ] [-c tdiary.confのあるディレクトリ]
+
+ 10. 自分の tDiary の好きな場所 (例えばヘッダ) に以下のようなフォームを加
+     えてください。
+
+     <form method="get" action="estraier-search.rb" class="search">
+     <input type="text" name="query" size="20" value="">
+     <input type="submit" value="Search">
+     </form>
+
+     search-form.rb プラグインを有効にしている場合は、以下のように書くこ
+     ともできます。
+
+     <%= search_form("estraier-search.rb", "query") %>
+
+  以上です。
+
+
+検索のしかた
+------------
+
+  estraier-search の検索対象は、日記本文、ツッコミ、TrackBack です。
+
+  検索方法については、
+  http://hyperestraier.sourceforge.net/uguide-ja.html#searchcond をご覧
+  ください。検索条件の書式を指定していない場合は、「簡便書式」で検索を行
+  います。
+
+
+連絡先
+------
+
+  かずひこ <kazuhiko@fdiary.net>
+  http://www.fdiary.net/
+
+  バグ報告は以下のどこかにお願いします。
+    * tdiary-devel ML
+    * 直接メール
Index: /tdiary/branches/upstream/contrib/util/estraier-search/ChangeLog
===================================================================
--- /tdiary/branches/upstream/contrib/util/estraier-search/ChangeLog (revision 710)
+++ /tdiary/branches/upstream/contrib/util/estraier-search/ChangeLog (revision 710)
@@ -0,0 +1,36 @@
+2007-02-23 SHIBATA Hiroshi <h-sbt@nifty.com>
+
+	* estraier-search.rb: add override whatsnew plugin.
+	
+2007-02-18  Kazuhiko  <kazuhiko@fdiary.net>
+
+	* estraier-search.rb: add 'require "date"'.
+
+2007-02-16  Kazuhiko  <kazuhiko@fdiary.net>
+
+	* estraier-register.rb, estraier-search.rb: rename
+	@last_modified attribute to @mdate. 
+
+	* estraier-register.rb: register all visible comments (ie. not
+	limit to 100).
+
+2007-02-15  Kazuhiko  <kazuhiko@fdiary.net>
+
+	* estraier-search.rb, estraier.rhtml: support similarity search.
+
+	* estraier-register.rb, estraier-search.rb: support
+	"estraier.with_user_name" option.
+	* estraier-search.rb, estraier.rhtml: support changing a phrase
+	format.
+
+2007-02-14  Kazuhiko  <kazuhiko@fdiary.net>
+
+	* estraier.rxml: add guid elements and escape contents of
+	description elements.
+	* estraier-register.rb, estraier-search.rb: add the
+	'estraier.path' variable.
+	* estraier.rhtml: revise for more tdiary-like outputs.
+
+2007-02-13  Kazuhiko  <kazuhiko@fdiary.net>
+
+	* initial release: ported from rast-search.
Index: /tdiary/branches/upstream/contrib/util/estraier-search/i.estraier.rhtml
===================================================================
--- /tdiary/branches/upstream/contrib/util/estraier-search/i.estraier.rhtml (revision 710)
+++ /tdiary/branches/upstream/contrib/util/estraier-search/i.estraier.rhtml (revision 710)
@@ -0,0 +1,31 @@
+<h1><%= _(@conf.html_title) %> [全文検索]</h1>
+<form action="<%= _(@cgi.script_name) %>">
+<p>
+<input type="text" name="query" value="<%= _(@conf.to_native(@query)) %>">
+<input type="submit" value="検索">
+</p>
+<p>
+並べ替え:
+<select name="sort">
+<%= @sort_options %>
+</select>
+<select name="order">
+<%= @order_options %>
+</select>
+表示件数:
+<select name="num">
+<%= @num_options %>
+</select>
+</p>
+</form>
+<% if @msg %>
+<p><%= @msg %></p>
+<% else %>
+<p><%= @result.doc_num %>件中<%= @start + 1 %>-<%= [@result.doc_num, @start + @num].min %>件目</p>
+<% for i in @start...@start+@num %><% item = @result.get_doc(i) || next %><% format_result_item(item) %>
+<h2><a href="<%= @conf.index %><%= @plugin.anchor(@date) %>"><%= _(@date.sub(/^(\d{4})(\d{2})(\d{2}).*/, "\\1-\\2-\\3")) %></a><%= @conf.to_native(@title) %>(スコア:<%= item.attr("#nodescore") %>)</h2>
+<% end %>
+<% if @result.doc_num > @num %>
+<%= format_links(@result) %>
+<% end %>
+<% end %>
Index: /tdiary/branches/upstream/contrib/util/estraier-search/ja/estraier-register.rb
===================================================================
--- /tdiary/branches/upstream/contrib/util/estraier-search/ja/estraier-register.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/util/estraier-search/ja/estraier-register.rb (revision 710)
@@ -0,0 +1,3 @@
+@estraier_register_conf_label = 'Estraier検索'
+@estraier_register_conf_header = 'Estraier検索インデックスの再構築'
+@estraier_register_conf_description = 'Estraier検索のインデックスを再構築する場合は、チェックボックスをチェックしてOKを押してください。'
Index: /tdiary/branches/upstream/contrib/util/posttdiary/posttdiary.rb
===================================================================
--- /tdiary/branches/upstream/contrib/util/posttdiary/posttdiary.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/util/posttdiary/posttdiary.rb (revision 710)
@@ -0,0 +1,240 @@
+#!/usr/bin/env ruby
+$KCODE= 'u'
+#
+# posttdiary: update tDiary via e-mail. $Revision: 1.5 $
+#
+# Copyright (C) 2002, All right reserved by TADA Tadashi <sho@spc.gr.jp>
+# You can redistribute it and/or modify it under GPL2.
+#
+
+def usage
+	<<-TEXT.gsub( /^\t{2}/, '' )
+		#{File::basename __FILE__}: update tDiary via e-mail.
+		usage: ruby #{File::basename __FILE__} [options] <url> [user] [passwd]
+		arguments:
+		  url:    update.rb's URL of your diary.
+		  user:   user ID of your diary updating.
+		  passwd: password of your diary updating.
+		          If To: field of the mail likes "user-passwd@example.com",
+		          you can omit user and passwd arguments.
+		options:
+		  --image-path,   -i: directory of image saving into.
+		  --image-url,    -u: URL of image.
+		          You have to specify both options when using images.
+		  --image-format, -f: format of image tag specified image serial
+		          number as '$0' and image url as '$1'.
+		          default format is ' <img class="photo" src="$1" alt="">'.
+		  --use-subject,  -s: use mail subject to subtitle.
+		          and insert image between subtitle and body.
+	TEXT
+end
+
+def image_list( date, path )
+	image_path = []
+	Dir.foreach( path ) do |file|
+		if file =~ /(\d{8,})_(\d+)\./ and $1 == date then
+			image_path[$2.to_i] = file
+		end
+	end
+	image_path
+end
+
+def bmp_to_png( bmp )
+	png = bmp.sub( /\.bmp$/, '.png' )
+	begin
+		require 'magick'
+		img = Magick::Image::new( bmp )
+		img.write( 'magick' => 'png', 'filename' => png )
+	rescue LoadError
+		system( "convert #{bmp} #{png}" )
+	end
+	if FileTest::exist?( png )
+		File::delete( bmp )
+		png
+	else
+		bmp
+	end
+end
+
+begin
+
+	raise usage if ARGV.length < 1
+
+	require 'getoptlong'
+	parser = GetoptLong::new
+	image_dir = nil
+	image_url = nil
+	image_format = ' <img class="photo" src="$1" alt="">'
+	use_subject = false
+	parser.set_options(
+		['--image-path', '-i', GetoptLong::REQUIRED_ARGUMENT],
+		['--image-url', '-u', GetoptLong::REQUIRED_ARGUMENT],
+		['--image-format', '-f', GetoptLong::REQUIRED_ARGUMENT],
+		['--use-subject', '-s', GetoptLong::NO_ARGUMENT]
+	)
+	begin
+		parser.each do |opt, arg|
+			case opt
+			when '--image-path'
+				image_dir = arg
+			when '--image-url'
+				image_url = arg
+			when '--image-format'
+				image_format = arg
+			when '--use-subject'
+				use_subject = true
+			end
+		end
+	rescue
+		raise usage
+	end
+	raise usage if (image_dir and not image_url) or (not image_dir and image_url)
+	image_dir.sub!( %r[/*$], '/' ) if image_dir
+	image_url.sub!( %r[/*$], '/' ) if image_url
+	url = ARGV.shift
+	if %r|http://([^:/]+)(?::(\d+))?(/.*)| =~ url then
+		host = $1
+		port = ($2 || 80).to_i
+		cgi  = $3
+	else
+		raise 'bad url.'
+	end
+
+	user = ARGV.shift
+	pass = ARGV.shift
+
+	require 'base64'
+	require 'nkf'
+	image_name = nil
+
+	mail = NKF::nkf( '-m0 -Xwd', ARGF.read )
+	raise "#{File::basename __FILE__}: no mail text." if not mail or mail.length == 0
+
+	head, body = mail.split( /(?:\r?\n){2}/, 2 )
+
+	if head =~ /Content-Type:\s*Multipart\/Mixed.*boundary=\"(.*?)\"/im then
+		if not image_dir or not image_url then
+			raise "no --image-path and --image-url options"
+		end
+
+		bound = "--" + $1
+		body_sub = body.split( Regexp.compile( Regexp.escape( bound ) ) )
+		body_sub.each do |b|
+			sub_head, sub_body = b.split( /(?:\r?\n){2}/, 2 )
+
+			next unless sub_head =~ /Content-Type:/i
+
+			if sub_head =~ %r[^Content-Type:\s*text/plain]i then
+				@body = sub_body
+			elsif sub_head =~ %r[
+				^Content-Type:\s*
+				(?:image/ | application/octet-stream).+
+				name=".+(\.[^.]+?)" (?# 1: extension)
+			]imx
+				image_ext = $1.downcase
+				now = Time::now
+				list = image_list( now.strftime( "%Y%m%d" ), image_dir )
+				image_name = now.strftime( "%Y%m%d" ) + "_" + list.length.to_s + image_ext
+				File::umask( 022 )
+				open( image_dir + image_name, "wb" ) do |s|
+					begin
+						s.print Base64::decode64( sub_body.strip )
+					rescue NameError
+						s.print decode64( sub_body.strip )
+					end
+				end
+				if /\.bmp$/i =~ image_name then
+					bmp_to_png( image_dir + image_name )
+					image_name.sub!( /\.bmp$/i, '.png' )
+				end
+				@image_name ||= []
+				@image_name << image_name
+			end
+		end
+	elsif head =~ /^Content-Type:\s*text\/plain/i
+		@body = body
+	else
+		raise "cannot read this mail"
+	end
+
+	if @image_name then
+		img_src = ""
+		@image_name.each do |i|
+			serial = i.sub( /^\d+_(\d+)\..*$/n, '\1' )
+			img_src += image_format.gsub( /\$0/, serial ).gsub( /\$1/, image_url + i )
+		end
+		if use_subject then
+			@body = "#{img_src}\n#{@body}".sub( /(?:\r?\n|\r)+\z/, "\n" )
+		else
+			@body = "#{@body}".sub( /(?:\r?\n|\r)+\z/, "\n" ) << img_src
+		end
+	end
+
+	addr = nil
+	if /^To:(.*)$/ =~ head then
+		addr = case to = $1.strip
+		when /.*?\s*<(.+)>/, /(.+?)\s*\(.*\)/
+			$1
+		else
+			to
+		end
+	end
+
+	if /([^-]+)-(.*)@/ =~ addr then
+		user ||= $1
+		pass ||= $2
+	end
+
+	raise "no user." unless user
+	raise "no passwd." unless pass
+
+	subject = ''
+	nextline = false
+	headlines = head.split( /(?:\r?\n|\r)+/ )
+	for n in 0 .. headlines.size-1
+		if nextline then
+			if /^[ \t]/ =~ headlines[n] then
+				s = headlines[n].sub( /^[ \t]/, '' )
+				subject += NKF::nkf( '-wXd', s )
+			else
+				break
+			end
+		end
+		if /^Subject:\s*(.+)$/i =~ headlines[n] then
+			subject = NKF::nkf( '-wXd', $1 )
+			nextline = true
+		end
+	end
+	if use_subject then
+		title = ''
+		@body = "#{subject}\n#{@body}"
+	else
+		title = subject
+	end
+
+	require 'cgi'
+	require 'nkf'
+	data = "title=#{CGI::escape title}"
+	data << "&body=#{CGI::escape @body}"
+	data << "&append=true"
+
+	require 'net/http'
+	Net::HTTP.start( host, port ) do |http|
+		auth = ["#{user}:#{pass}"].pack( 'm' ).strip
+		res, = http.get( cgi, {
+				'Authorization' => "Basic #{auth}",
+				'Referer' => url })
+		if %r|<input type="hidden" name="csrf_protection_key" value="([^"]+)">| =~ res.body then
+			data << "&csrf_protection_key=#{CGI::escape( CGI::unescapeHTML( $1 ) )}"
+		end
+		res, = http.post( cgi, data, {
+				'Authorization' => "Basic #{auth}",
+				'Referer' => url })
+	end
+
+rescue
+	$stderr.puts $!
+	$stderr.puts $@.join( "\n" )
+	File::delete( image_dir + image_name ) if image_dir and image_name and FileTest::exist?( image_dir + image_name )
+	exit 1
+end
Index: /tdiary/branches/upstream/contrib/util/posttdiary/README.ja
===================================================================
--- /tdiary/branches/upstream/contrib/util/posttdiary/README.ja (revision 710)
+++ /tdiary/branches/upstream/contrib/util/posttdiary/README.ja (revision 710)
@@ -0,0 +1,12 @@
+posttdiary.rb is a updater of diary via E-mail with images.
+
+see:
+	http://tdiary-users.sourceforge.jp/cgi-bin/wiki.cgi?posttdiary%2Erb
+
+--
+
+posttdiary-ex.rb is a more enhanced posttdiary.rb.
+
+see:
+	http://ks.nwr.jp/wiki/wiki.cgi?posttdiary%2erb%b2%fe%c2%a4 (Japanese)
+	http://ks.nwr.jp/prog/posttdiary.html (English)
Index: /tdiary/branches/upstream/contrib/util/posttdiary/posttdiary-ex.rb
===================================================================
--- /tdiary/branches/upstream/contrib/util/posttdiary/posttdiary-ex.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/util/posttdiary/posttdiary-ex.rb (revision 710)
@@ -0,0 +1,1137 @@
+#!/usr/bin/env ruby
+$KCODE= 'u'
+#
+# posttdiary-ex: update tDiary via e-mail. $Revision: 1.6.2.4 $
+#
+# Copyright (C) 2002, All right reserved by TADA Tadashi <sho@spc.gr.jp>
+# You can redistribute it and/or modify it under GPL2.
+#
+# 2007.9.22: v.1.66: Modified by K.Sakurai (http://ks.nwr.jp)
+#  Acknowledgements:
+#   * Based on posttdiary.rb v1.2 by TADA.
+#   * Some codes partially imported from Enikki Plugin Ex. : 
+#     http://shimoi.s26.xrea.com/hiki/hiki.cgi?TdiaryEnikkiEx
+#   * Thanks to taketori for image size detection method.
+#   * Thanks to NOB for debugging.
+#   * Thanks to tamo (http://tamo.tdiary.net) for image-POSTing codes & testing.
+#
+
+#----------------------------------------------
+
+def usage( detailed_help )
+	# (if "!" is at the head of the line, it is to be shown only when detailed_help == true (-h option) )
+	text = <<-TEXT
+		#{File::basename __FILE__}: update tDiary via e-mail (v1.64).
+		usage: ruby posttdiary-ex.rb [options (without -d)] <url> <user> <passwd>
+		       ruby posttdiary-ex.rb [options (with -d)]
+		arguments:
+		  url:    update.rb's URL of your diary
+		  user:   username for your tDiary system
+		  passwd: password for your tDiary system
+!		          If the To: field is  formatted as "user-passwd@example.com",
+!		          you can omit user and passwd arguments.
+		options:
+!		  ============ for automatic configuration ==========
+		  --read-conffile,   -a dirname: read settings from (dirname)/tdiary.conf
+!		          Reads configuration parameters of image_ex plugin as well.
+!		          (The default values of -i, -u, -t, -z, -o, -y, -C can be imported.)
+!		          Specify the localtion (or fullpathname) of tDiary conf file.
+!		          ex. -a /home/hoge/htdocs/diary/tdiary.conf
+!
+!		  ============ basic options ==============
+		  --image-path,      -i dirname: directory to store the image(s) in.
+		  --image-url,       -u URL: URL of the image directory.
+!		          You must specify both -i and -u options
+!		          (unless they are available from tdiary.conf + image_ex plugin)
+!		          When using --remote-mode or --use-image-ex, -u is not required.
+		  --use-subject,     -s: use mail subject as subtitle
+!		          Also inserts attached image(s) between subtitle and body.
+		  --make-thumbnail,  -t size: Create thumbnail with a link to the original image
+!		          Works only when the original image is larger than the specified size
+!		          (see --threshold-size also)
+!		          ex. -t 80x80
+		  --image-geometry,  -g size: resize image(s).
+!		          The original image would be overwritten.
+!		          Does not change the image size when the original image is smaller.
+!		          ex. -g 800x800 (change the image size to "fit in" to 800x800 pixels)
+		  --use-image-ex,    -e: Recognize & auto-generate tags for image.rb (Enikki)
+		          or image_ex.rb (Enikki ex.) plugin
+!		          Tag format: <%=image (serialno),"(alt text)"%>
+!		          Serialno starts from 0. Will be automatically increased to match
+!		          the real filename.
+!		          Overrides -f option.
+		  --wiki-style,      -w: output image tags in Wiki style
+!		          Suppress adding a whitespace before each tag
+!		          Adds "!" to subject when -s option is given
+!		          Recognize Wiki style tags and rewrite
+!		          Must be used with image.rb or image_ex.rb plugin.
+		  --blog-style,      -B: do not specify date to append unless specified
+!			  Suitable for use with blogkit
+		  --read-exif,       -c: read "User Comment" tag from EXIF and use as ALT text
+!		          If not specified, filename would be used as ALT text.
+!		          Requires libexif and "exif" command.
+		  --hour-offset,     -o offset: hour_offset of tDiary
+!		          (ex. -o +4  (do not change date until 28:00))
+		  --yearly-dir,      -y: put images in yearly separated directories
+!		                         ( 2004/ , 2005/ , ...)
+		  --help,            -h: show detailed help & advanced options
+!
+!		  ============ advanced options ==============
+!		  --convert-path,    -C fullpath_of_convert: location of "convert" command
+!		          Use this option when ImageMagick's commands are not path-reachable.
+!		          Assumes the same location for "identify" command as well.
+!		  --exif-path,       -E fullpath_of_exif: location of "exif" command
+!		          Enables --read-exif command as well.
+!		          Use this option when "exif" command is not path-reachable.
+!		  --remote-mode,     -R: upload images via update.rb using HTTP POST.
+!		          Allows user to separate the mailserver and the webserver.
+!		          Note: Thumbnails would not be posted.
+!		  --remote-image-path,-D remote_dirname:
+!		          Specify the image directory of the remote webserver.
+!		          Required when using image_ex.rb with --remote-mode option.
+!		  --remote-yearly-dir,-Y switch:
+!		          Specify whether to put images in yearly separated directories
+!		          at the remote webserver.
+!		          Required when image_ex.rb with --remote-mode option.
+!		          0: do not separate,  1: separate
+!		  --preserve-local-images,   -P:
+!		          Do not delete local image files.
+!		          Effective only when --remote-mode is enabled.
+!		  --upload-only,     -U:
+!		          Upload the attached images to server, but do not update the diary.
+!		          Also possible by adding "_UPLONLY#" to mail body.
+!		  --group-id,        -G: specify the group name (or GID) of the image file.
+!		          Also makes the file group writable (chmod 664).
+!		          ex1. -G www
+!		          ex2. -G 67
+!		  --class-name,      -n class_name:
+!		          Class name for each photo (default: photo)
+!		          Invalid when --use-image-ex or --wiki-style option is enabled.
+!		  --add-div,         -v number_of_images:
+!		          Encapsule all attached images with <div class="photos">...</div>
+!		          When specified number of (or more) images are attached.
+!		          Set to 2 when not given. Specify 0 to disable.
+!		          Automatically set to 0 when --wiki-style is enabled.
+!		          ex. -v 3 (works only when 3 or more images are attached)
+!		  --threshold-size,  -z threshold_image_size:
+!		          Make thumbnail if image size is larger.
+!		          ex1. -z 120x140
+!		          ex2. -z 140   (same to 140x140)
+! 		  --image-format,    -f format string:
+!		          Specify the format string of the image tag
+!		          These variables can be used in the format string: 
+!		              $0 : image serial number
+!		              $1 : image url
+!		              $2 : thumbnail image url (when -t is specified)
+!		              $3 : class name
+!		              $4 : ALT text (filename, or EXIF comment when -c is specified)
+!			  ex. -f \\\"{{image \\\$0}}\\\"
+!		  --use-original-name, -r:
+!		          use original filename as ALT text when not specified
+!		  --pass-filename,   -p:
+!		          Pass real filename (instead of serialno) to image_ex plugin
+!		          (EXPERIMENTAL: Has no meanings so far)
+!		          Effective only with -e option.
+!		  --filter-mode,    -d: print to stdout (does not call update.rb)
+!		  --write-to-file,  -b filename: writeout to file (does not call update.rb)
+!		  --date-margin,    -j date_margin: avoid writing diaries for future dates
+!		          ex. -j 30 (default=7, 0=disabled)
+!		  --rotate, -T LEFT or RIGHT: rotate images
+!			  ex. -T RIGHT (rotate 90degrees clockwise)
+!		          Also possible by adding "_ROT_LEFT#" or "_ROT_RIGHT#"to mail body.
+!
+!		Output format:
+!		  without -e/f, without -t: <img src="$1" class="photo" alt="$4" title="$4">
+!		  without -e/f, with -t: <A HREF="$1"><img src="$2" class="photo" alt="$4" title="$4"></a>
+!		  with -e: <%=image $0,'$4'%>
+!		  with -w: {{image $0,'$4'}}  (overrides -e)
+!		  with -f: (specified format) (overrides -e, -w)
+!
+!		Date specification format in mail body text:
+!		  ex. when you want to append this mail to "2005 Feb 15" 's diary,
+!                     add this line to mail body:
+!
+!		_Date#2005-2-15
+!
+		Examples:
+		  posttdiary-ex.rb -a /home/hoge/htdocs/diary/tdiary.conf http://yoursite.jp/~hoge/diary/update.rb (tDiary username) (passwd)
+		  posttdiary-ex.rb -w -i /home/hoge/htdocs/diary-images/ -y -t 120x120 -s -g 800x800 http://yoursite.jp/~hoge/diary/update.rb (tDiary username) (passwd)
+!		  posttdiary-ex.rb -i /home/hoge/htdocs/diary-images/ -u http://yoursite.jp/~hoge/diary-images/ -t 120x120 -s -g 800x800 http://yoursite.jp/~hoge/diary/update.rb (tDiary username) (passwd)
+!		  posttdiary-ex.rb -R -i /home/hoge/tmp -D /home/hoge/htdocs/diary-images -Y 1 -s -g 800x800 http://yoursite.jp/~hoge/diary/update.rb (tDiary username) (passwd)
+
+  TEXT
+  if( detailed_help )
+	text.gsub!( /\!/, '' )
+  else
+	text.gsub!( /\![^\r\n]*[\r\n]+/, '' )
+  end
+  text.gsub( /\t/, '' )
+end
+
+#--- override functions in the original tdiary.rb
+def base_url
+	return ''
+end
+
+def TDiaryError( msg )
+	print msg + "\n"
+	exit 0
+end
+
+def load_cgi_conf
+	raise TDiaryError, 'posttdiary-ex: No @data_path variable.' unless @data_path
+	@data_path = add_delimiter( @data_path )
+	raise TDiaryError, 'posttdiary-ex: Do not set @data_path as same as tDiary system directory.' if @data_path == @tdiary_dirname
+	
+	variables = [:author_name, :author_mail, :index_page, :hour_offset]
+	begin
+		cgi_conf = File::open( "#{@data_path}tdiary.conf" ){|f| f.read }
+		cgi_conf.untaint unless @secure
+		eval( cgi_conf, binding, "(cgi_conf)", 1 )
+		variables.each do |var| eval "@#{var} = #{var} if #{var} != nil" end
+	rescue IOError, Errno::ENOENT
+	end
+end
+
+#--- read tdiary.conf
+def read_tdiary_conf( dfname )
+	if test( ?d , dfname ) then
+		@tdiary_dirname = dfname
+		@tdiary_conf_file = 'tdiary.conf'
+	elsif test( ?f , dfname ) then
+		dfname =~ /(.*)[\/\\]([^\/\\]+)/
+		@tdiary_dirname = $1
+		@tdiary_conf_file = $2
+	end
+	@tdiary_dirname = add_delimiter( @tdiary_dirname )
+	orgdir = Dir.pwd
+	Dir.chdir( @tdiary_dirname )
+
+	@secure = false
+	@options = {}
+	# evaluate tdiary.conf  (load_cgi_conf() would be called as well, via tdiary.conf)
+	eval( File::open( @tdiary_dirname + @tdiary_conf_file ){|f| f.read }.untaint, binding, "(tdiary.conf)", 1 )
+
+	Dir.chdir( orgdir )
+	true;
+
+	rescue IOError, Errno::ENOENT
+	raise 'posttdiary-ex: failed to read tdiary configuration file'
+end
+
+def check_local_images( date, path )
+	available_list = []
+	exist_list = []
+	maxnum = -1
+	Dir.foreach( path ) do |file|
+		if file =~ /(\d{8,})_(\d+)\.([^\.]*)/ then
+			if $1 == date then
+				serial = $2.to_i
+				maxnum = serial if serial > maxnum
+				exist_list[serial]=true
+			end
+		end
+	end
+
+	num = 0
+	for i in 0 .. 200
+		if !exist_list[i] then
+			available_list[num] = i
+			num += 1
+		end
+	end
+
+	maxnum += 1
+	[maxnum, available_list]
+end
+
+def bmp_to_png( bmp )
+	png = bmp.sub( /\.bmp$/, '.png' )
+	stat = system( "#{@convertpath} #{bmp} #{png}" )
+	raise "posttdiary-ex: could not run convert command (#{@convertpath})" if !stat
+	if FileTest::exist?( png )
+		File::delete( bmp )
+		png
+	else
+		bmp
+	end
+end
+
+def check_command( cmdname )
+	raise 'posttdiary-ex: program bug found in check_command() (call the programmer!)' unless cmdname
+
+	if @pt_exist_cmd then
+		for priv in @pt_exist_cmd
+			if priv == cmdname then
+				 return true
+			end
+		end
+	end
+
+	stat = false
+	if ( test( ?x , cmdname ) ) then
+		 stat = true
+	else
+		require 'shell'
+		sh = Shell.new
+		searchdir = sh.system_path
+		for dir in searchdir
+			fullpath = add_delimiter( dir ) + cmdname
+			if sh.executable?(fullpath) then
+				stat = true
+				break
+			end
+		end
+	end
+	if stat then
+		@pt_exist_cmd = [] unless @pt_exist_cmd
+		@pt_exist_cmd << cmdname
+	else
+		raise "posttdiary-ex: execution failed: #{cmdname} not found"
+	end
+	stat
+end
+
+def check_image_size( name, geo )
+	cmdstr = @magickpath + "identify"
+	check_command( cmdstr )
+	begin
+		imgsize = %x[#{cmdstr} '#{name}'].sub(/#{name}/, '').split[1][/\d+x\d+/]
+		i = imgsize.split(/x/)
+		j = geo.split(/x/)
+		return false if !i[1] or !j[1]
+		return false if i[0].to_i < j[0].to_i and i[1].to_i < j[1].to_i
+
+		rescue
+		return false
+	end
+	true
+end
+
+def change_image_size( org, geo )
+	check_command( @convertpath )
+	system( "#{@convertpath} -size #{geo}\\\> #{org} -geometry #{geo}\\\> #{org}" )
+	if FileTest::exist?( org )
+		org
+	else
+		""
+	end
+end
+
+def read_exif_comment( fullpath_imgname )
+	require 'nkf'
+	v = ""
+	check_command( @exifpath )
+	open( "| #{@exifpath} -t \"User Comment\" #{fullpath_imgname}", "r" ) do |f|
+		s = f.readlines
+		s.each do |t|
+			t.gsub!( /.*Value:/, '' )
+			v = NKF::nkf( '-m0 -wXd', t ).gsub!( /^\s+/, '' ).chomp! if $&
+		end
+	end
+	v = '' if v =~ /^\(null\)$/i
+	v
+end
+
+def read_exif_orientation( fullpath_imgname )
+	# returns orientaion value
+	#  top-left : 1
+	#  right-top : 6
+	#  left-bottom : 8
+	#  bottom-right : 3
+	val = 1
+	v = ''
+	check_command( @exifpath )
+	open( "| #{@exifpath} -t \"Orientation\" #{fullpath_imgname}", "r" ) do |f|
+		s = f.readlines
+		s.each do |t|
+			t.gsub!( /.*Value:/, '' )
+			if $& then
+				v = t
+				break
+			end
+		end
+	end
+	val = 6 if v =~ /right.+top/i
+	val = 8 if v =~ /left.+bottom/i
+	val = 3 if v =~ /bottom.+right/i
+	val
+end
+
+def rotation_degree( ori )
+	deg = 0
+	deg = 90 if ori == 6
+	deg = -90 if ori == 8
+	deg = 180 if ori == 3
+	deg
+end
+
+def rotate_image( org, deg )
+	if FileTest::exist?( org ) then
+		check_command( @convertpath )
+		system( "#{@convertpath} -rotate #{deg} #{org} #{org}" )
+	end
+	if FileTest::exist?( org )
+		org
+	else
+		""
+	end
+end
+
+def make_thumbnail( idir, iname , newsize , gid )
+	org_full = idir + iname
+	tb_name = "s" + iname
+	tb_full = idir + tb_name
+	begin
+		check_command( @convertpath )
+		# only for imagemagick 6 and later!!
+		system( "#{@convertpath} -thumbnail #{newsize}\\\> #{org_full} #{tb_full}" )
+	end
+	if FileTest::exist?( tb_full )
+		if gid then
+			require 'shell'
+			sh = Shell.new
+			sh.chown( nil , gid , tb_full )
+			sh.chmod( 00664 , tb_full )
+		end
+		tb_name
+	else
+		iname
+	end
+end
+
+def add_body_text( prev, sub_head , sub_body )
+	addtext = prev
+	if prev.size > 0 and !(prev =~ /\n$/) then 
+		addtext += "\n"
+	end
+	if sub_head =~ %r[^Content-Transfer-Encoding:\s*base64]i then
+		addtext += NKF::nkf( '-wXd -mB', sub_body )
+	elsif
+		addtext += sub_body
+	end
+	addtext
+end
+
+def add_delimiter( orgpath )
+	if !orgpath or orgpath.size < 1 then
+		newpath = ""
+	else
+		if !(orgpath =~ /[\/\\]$/) then
+			if !(orgpath =~ /\//) and orgpath =~ /\\/ then
+				newpath = orgpath + "\\"
+			else
+				newpath = orgpath + "/"
+			end
+		else
+			newpath = orgpath.dup
+		end
+	end
+	newpath
+end
+
+def make_image_body( imgdata , imgname , remotedir , now , image_boundary, protection_key )
+	fname = ""
+	extension = ""
+	image_body = ""
+        if imgname =~ /^(.*)(\.jpg|\.jpeg|\.gif|\.png)\z/i
+	        extension = $2.downcase
+		fname = $1 + extension
+	else
+		return nil
+	end
+	typestr = "image/jpeg" if extension =~ /jpe??g/i
+	typestr = "image/bmp"  if extension =~ /bmp/i
+	typestr = "image/gif"  if extension =~ /gif/i
+	typestr = "image/png"  if extension =~ /png/i
+
+	if remotedir and remotedir.length > 0 then
+image_body.concat <<END
+--#{image_boundary}\r
+content-disposition: form-data; name="plugin_image_dir"\r
+\r
+#{add_delimiter(remotedir)}\r
+END
+	end
+	if protection_key and protection_key.length > 0 then
+image_body.concat <<END
+--#{image_boundary}\r
+content-disposition: form-data; name="csrf_protection_key"\r
+\r
+#{protection_key}\r
+END
+	end
+
+image_body.concat <<END
+--#{image_boundary}\r
+content-disposition: form-data; name="plugin_image_add"\r
+\r
+true\r
+--#{image_boundary}\r
+content-disposition: form-data; name="plugin_image_addimage"\r
+\r
+true\r
+--#{image_boundary}\r
+content-disposition: form-data; name="date"\r
+\r
+#{now.strftime( "%Y%m%d" )}\r
+END
+
+image_body.concat <<END
+--#{image_boundary}\r
+content-disposition: form-data; name="plugin_image_file"; filename="#{fname}"\r
+Content-Type: #{typestr}\r
+\r
+#{imgdata}\r
+END
+
+image_body.concat <<END
+--#{image_boundary}\r
+content-disposition: form-data; name="plugin"\r
+\r
+image\r
+--#{image_boundary}--\r
+END
+	image_body
+end
+
+def check_remote_images( http, cgi, user, pass, now )
+	available_list = []
+	maxnum = -1
+
+	str = cgi + '?edit=true;year=' + now.strftime( "%Y" ) + ';month=' + now.strftime( "%m").gsub(/^0/, "") + ';day=' + now.strftime( "%d").gsub(/^0/, "")
+	req = Net::HTTP::Get.new( str )
+	req.basic_auth user, pass
+	response, = http.request(req)
+	body = response.body
+	date = now.strftime( "%Y%m%d" )
+	imglist = []
+	num = 0
+	for i in 0 .. 200
+		if body =~ /\<img [^\<\>]*src=\"[^\"]*(#{date}_#{i}\.[^\"]*)\"/ then
+			maxnum = i
+		else
+			available_list[num] = i
+			num += 1
+		end
+	end
+
+	maxnum += 1
+	[maxnum, available_list]
+end
+
+def post_image( http, cgi, user, pass, image_dir , imgname, remote_image_dir, now, protection_key, refurl )
+	auth = ["#{user}:#{pass}"].pack( 'm' ).strip
+	image_boundary = "PosttdiaryMainBoundary"
+	image_data = ( File.open( image_dir + imgname ) { |f| f.read } )
+	image_body = make_image_body(image_data, imgname, remote_image_dir, now , image_boundary, protection_key ) if image_data
+	if image_body then
+		image_header = {
+		    'Authorization' => "Basic #{auth}",
+		    'Content-Length' => image_body.length.to_s,
+		    'Content-Type' => "multipart/form-data; boundary=#{image_boundary}",
+		    'Referer' => refurl,
+		}
+		response, = http.post( cgi, image_body, image_header )
+		raise "posttdiary-ex: failed to upload image (#{imgname}) to remote server" if response.code.to_i < 200 or response.code.to_i > 202
+	end
+
+	(image_body ? true : false)
+end
+
+def get_date_to_append( http, cgi, user, pass, now )
+	# call update.rb via HTTP and get the date to append
+	str = cgi
+	req = Net::HTTP::Get.new( str )
+	req.basic_auth user, pass
+	response, = http.request(req)
+	body = response.body
+
+	year = now.strftime( "%Y" )
+	month = now.strftime( "%m" )
+	day = now.strftime( "%d" )
+	bodytmp = body.split(/$/);
+	bodytmp.each do |oneline|
+		if oneline =~ /\<input\s.*\sname=\"year\"([^\>]*)\>/ then
+			if $1 =~ /value=\"(\d\d\d\d)"/ then
+				year = $1
+			end
+		end
+		if oneline =~ /\<input\s.*\sname=\"month\"([^\>]*)\>/ then
+			if $1 =~ /value=\"(\d+)\"/ then
+				month = $1
+			end
+		end
+		if oneline =~ /\<input\s.*\sname=\"day\"([^\>]*)\>/ then
+			if $1 =~ /value=\"(\d+)\"/ then
+				day = $1
+			end
+		end
+	end
+
+	Time::local( year, month, day )
+end
+
+def parse_mail( head, body , image_dir )
+	imglist = []
+	orglist = []
+	textbody = ""
+	imgnum = -1
+	imgdir = add_delimiter( image_dir )
+
+	if   head =~ /Content-Type:\s*Multipart\/Mixed.*boundary=\"*([^\"\r\n]*)\"*/im or head =~ /Content-Type:\s*Multipart\/Related.*boundary=\"*([^\"\r\n]*)\"*/im then
+		bound = "--" + $1
+		body_sub = body.split( Regexp.compile( Regexp.escape( bound ) ) )
+		body_sub.each do |b|
+			sub_head, sub_body = b.split( /\r*\n\r*\n/, 2 )
+			sub_body = "" unless sub_body
+
+			next unless sub_head =~ /Content-Type/i
+
+			if sub_head =~ %r[^Content-Type:\s*text/plain]i then
+				textbody = add_body_text( textbody , sub_head, sub_body )
+			elsif sub_head =~ %r[^Content-Type:\s*(image\/|application\/octet-stream).*name=\"*(.*)(\.[^\"\r\n]*)\"*]im
+				imgnum += 1
+				orgname = $2
+				orgname = "" if !orgname
+				image_ext = $3.downcase
+				image_name = "_tmp" + Process.pid.to_s + "_" + imgnum.to_s + image_ext
+				File::umask( 022 )
+				open( imgdir + image_name, "wb" ) do |s|
+					begin
+						s.print Base64::decode64( sub_body.strip )
+					rescue NameError
+						s.print decode64( sub_body.strip )
+					end
+				end
+				if /\.bmp$/i =~ image_name then
+					bmp_to_png( imgdir + image_name )
+					image_name.sub!( /\.bmp$/, '.png' )
+				end
+				imglist[imgnum] = imgdir + image_name
+				orglist[imgnum] = orgname
+			end
+		end
+	elsif head =~ /^Content-Type:\s*text\/plain/i 
+		textbody = add_body_text( textbody , head, body )
+	else
+		raise "posttdiary-ex: can not read this mail (illegal format)"
+	end
+
+	addr = nil
+	if /^To:(.*)$/ =~ head then
+		to = $1.strip
+		if /.*?\s*<(.*)>/ =~ to then
+			addr = $1
+		elsif /(.*?)\s*\(.*\)/ =~ to
+			addr = $1
+		else
+			addr = to
+		end
+	end
+
+	subject = ''
+	nextline = false
+	headlines = head.split( /[\r\n]+/ )
+	for n in 0 .. headlines.size-1
+		if nextline then
+			if /^[ \t]/ =~ headlines[n] then
+				s = headlines[n].sub( /^[ \t]/, '' )
+				subject += NKF::nkf( '-wXd', s )
+			else
+				break
+			end
+		end
+		if /^Subject:(.*)$/ =~ headlines[n] then
+			s = $1.sub( /^\s+/, '' )
+			subject = NKF::nkf( '-wXd', s )
+			nextline = true
+		end
+	end
+
+	[addr, subject, imglist, orglist, textbody]
+end
+
+begin
+	raise usage(false) if ARGV.length < 1
+
+	require 'getoptlong'
+	parser = GetoptLong::new
+
+	conf_df_name = nil
+	image_dir = nil
+	image_url = nil
+	use_subject = false
+	thumbnail_size = nil
+	image_geometry = nil
+	use_image_ex = false
+	hour_offset = nil
+	@hour_offset = nil
+	yearly_dir = false
+	thumbnail_name = Hash.new("")
+	exif_comment = Hash.new("")
+	exif_orientation = Hash.new(1)
+	image_orgname = Hash.new("")
+
+	remote_mode = false
+	remote_image_dir = nil
+	remote_yearly_dir = false
+	preserve_local_images = false
+	upload_only = false
+	class_name = 'photo'
+	group_id = nil
+	add_div_imgnum = 2
+	add_div_imgnum_specified = nil
+	threshold_size = nil
+	pass_filename = false
+	filter_mode = false
+	writeout_filename = nil
+	read_exif = false
+	image_format = ' <img class="$3" src="$1" alt="$4" title="$4">'
+	image_format_with_thumbnail = ' <A HREF="$1"><img class="$3" src="$2" alt="$4" title="$4"></a>'
+	image_format_specified = nil
+	wiki_style = false
+	blog_style = false
+	use_original_name = false
+	date_margin = 7
+	convertpath_specified = nil
+	@convertpath = "convert"
+	@magickpath = ""
+	exifpath_specified = nil
+	@exifpath = "exif"
+	rotation_degree_specified = nil
+
+	parser.set_options(
+		['--read-conffile', '-a', GetoptLong::REQUIRED_ARGUMENT],
+		['--image-path', '-i', GetoptLong::REQUIRED_ARGUMENT],
+		['--image-url', '-u', GetoptLong::REQUIRED_ARGUMENT],
+		['--use-subject', '-s', GetoptLong::NO_ARGUMENT],
+		['--make-thumbnail', '-t', GetoptLong::REQUIRED_ARGUMENT],
+		['--image-geometry', '-g', GetoptLong::REQUIRED_ARGUMENT],
+		['--use-image-ex', '-e', GetoptLong::NO_ARGUMENT],
+		['--hour-offset', '-o', GetoptLong::REQUIRED_ARGUMENT],
+		['--yearly-dir', '-y', GetoptLong::NO_ARGUMENT],
+		['--help', '-h', GetoptLong::NO_ARGUMENT],
+		['--convert-path', '-C', GetoptLong::REQUIRED_ARGUMENT],
+		['--exif-path', '-E', GetoptLong::REQUIRED_ARGUMENT],
+		['--remote-mode', '-R', GetoptLong::NO_ARGUMENT],
+		['--remote-image-path', '-D', GetoptLong::REQUIRED_ARGUMENT],
+		['--remote-yearly-dir', '-Y', GetoptLong::REQUIRED_ARGUMENT],
+		['--preserve-local-images', '-P', GetoptLong::NO_ARGUMENT],
+		['--upload-only', '-U', GetoptLong::NO_ARGUMENT],
+		['--group-id', '-G', GetoptLong::REQUIRED_ARGUMENT],
+		['--class-name', '-n', GetoptLong::REQUIRED_ARGUMENT],
+		['--add-div', '-v', GetoptLong::REQUIRED_ARGUMENT],
+		['--threshold-size', '-z', GetoptLong::REQUIRED_ARGUMENT],
+		['--image-format', '-f', GetoptLong::REQUIRED_ARGUMENT],
+		['--use-original-name', '-r', GetoptLong::NO_ARGUMENT],
+		['--wiki-style', '-w', GetoptLong::NO_ARGUMENT],
+		['--blog-style', '-B', GetoptLong::NO_ARGUMENT],
+		['--read-exif', '-c', GetoptLong::NO_ARGUMENT],
+		['--margin-time', '-m', GetoptLong::REQUIRED_ARGUMENT],
+		['--pass-filename', '-p', GetoptLong::NO_ARGUMENT],
+		['--filter-mode', '-d', GetoptLong::NO_ARGUMENT],
+		['--write-to-file', '-b', GetoptLong::REQUIRED_ARGUMENT],
+		['--date-margin', '-j', GetoptLong::REQUIRED_ARGUMENT],
+		['--rotate', '-T', GetoptLong::REQUIRED_ARGUMENT]
+	)
+	begin
+		parser.each do |opt, arg|
+			case opt
+			when '--read-conffile'
+				conf_df_name = arg
+			when '--image-path'
+				image_dir = arg
+			when '--image-url'
+				image_url = arg
+			when '--use-subject'
+				use_subject = true
+			when '--make-thumbnail'
+				thumbnail_size = arg
+			when '--image-geometry'
+				image_geometry = arg
+			when '--use-image-ex'
+				use_image_ex = true
+			when '--hour-offset'
+				hour_offset = arg.to_i
+			when '--yearly-dir'
+				yearly_dir = true
+			when '--help'
+				print usage(true)
+				exit 0
+
+			when '--convert-path'
+				convertpath_specified = arg
+			when '--exif-path'
+				exifpath_specified = arg
+				read_exif = true
+			when '--remote-mode'
+				remote_mode = true
+			when '--remote-image-path'
+				remote_image_dir = add_delimiter(arg)
+			when '--remote-yearly-dir'
+				remote_yearly_dir = (arg =~ /[1yt]/i)
+			when '--preserve-local-images'
+				preserve_local_images = true
+			when '--upload-only'
+				upload_only = true
+				filter_mode = true
+			when '--group-id'
+				if arg =~ /\D/ then
+					require 'etc'
+					group_id = Etc.getgrnam( arg )['gid']
+				else
+					group_id = arg.to_i
+				end
+				group_id = nil if group_id <= 0 or group_id > 65535
+			when '--add-div'
+				add_div_imgnum_specified = arg.to_i
+			when '--threshold-size'
+				threshold_size = arg
+			when '--image-format'
+				image_format_specified = arg
+			when '--use-original-name'
+				use_original_name = true
+			when '--wiki-style'
+				wiki_style = true
+				use_image_ex = true
+			when '--blog-style'
+				blog_style = true
+			when '--read-exif'
+				read_exif = true
+			when '--pass-filename'
+				pass_filename = true
+			when '--filter-mode'
+				filter_mode = true
+			when '--write-to-file'
+				filter_mode = true
+				writeout_filename = arg
+			when '--date-margin'
+				date_margin = arg.to_i
+			when '--rotate'
+				rotation_degree_specified = 0
+				rotation_degree_specified = 90 if arg =~ /right/i
+				rotation_degree_specified = -90 if arg =~ /left/i
+			end
+		end
+	rescue
+		raise usage(false)
+	end
+	if conf_df_name then
+		if read_tdiary_conf( conf_df_name ) then
+			image_dir = @options['image.dir'] if @options['image.dir'] and !image_dir
+			image_url = @options['image.url'] if @options['image.url'] and !image_url
+			yearly_dir = true if @options['image_ex.yearlydir'] and @options['image_ex.yearlydir'] == 1
+			thumbnail_size = @options['image_ex.convertedwidth'].to_s + "x" + @options['image_ex.convertedheight'].to_s if @options['image_ex.convertedwidth'] and @options['image_ex.convertedheight'] and thumbnail_size == nil
+			threshold_size = @options['image_ex.thresholdsize'].to_s if @options['image_ex.thresholdsize'] and !threshold_size
+			@convertpath= @options['image_ex.convertpath'] if @options['image_ex.convertpath'] and !convertpath_specified
+			@exifpath = @options['image_ex.exifpath'] if @options['image_ex.exifpath'] and !exifpath_specified
+		else
+			conf_df_name = ''
+		end
+	end
+	image_url = "" if use_image_ex and !image_url
+	raise 'posttdiary-ex: image-path (-i) or image-url (-u) missing...' if (!image_url and image_dir) or (!image_dir and image_url)
+	if image_dir then
+		image_dir = add_delimiter( image_dir )
+	end
+	if image_url then
+		image_url += '/' unless %r[/$] =~ image_url
+	end
+	if thumbnail_size then
+		thumbnail_size.gsub!(/[\>\\\s]/, '')
+		thumbnail_size = thumbnail_size + 'x' + thumbnail_size if !(thumbnail_size =~ /x/ )
+		raise usage if !(thumbnail_size =~ /^\d+x\d+/)
+	end
+	threshold_size = thumbnail_size if !threshold_size
+	if threshold_size and threshold_size.size > 0 then
+		threshold_size.gsub!(/[\>\\\s]/, '')
+		threshold_size = threshold_size + 'x' + threshold_size if !(threshold_size =~ /x/ )
+		raise usage if !(threshold_size =~ /^\d+x\d+/)
+	end
+	if image_geometry then
+		image_geometry.gsub!(/[\>\\\s]/, '')
+		image_geometry = image_geometry + 'x' + image_geometry if !(image_geometry =~ /x/ )
+		raise usage if !(image_geometry =~ /^\d+x\d+/)
+	end
+	hour_offset = @hour_offset if !hour_offset
+	hour_offset = 0 if !hour_offset
+	if image_format_specified then
+		image_format = image_format_specified
+		image_format_with_thumbnail = image_format
+	else
+		if use_image_ex then
+			image_format = '<%=image $0,\'$4\'%>'
+			image_format_with_thumbnail = image_format
+		end
+		if wiki_style then
+			image_format = '{{image $0,\'$4\'}}'
+			image_format_with_thumbnail = image_format
+		end
+	end
+	if wiki_style then
+		add_div_imgnum = 0
+	else
+		add_div_imgnum = add_div_imgnum_specified if add_div_imgnum_specified
+	end
+	@convertpath = convertpath_specified 	if convertpath_specified
+	@convertpath = add_delimiter( @convertpath ) + "convert" if test( ?d , @convertpath )
+	@magickpath = $1 if @convertpath =~ /(.*[\/\\])[^\/\\]+$/
+	@exifpath = exifpath_specified 	if exifpath_specified
+	@exifpath = add_delimiter( @exifpath ) + "exif" if test( ?d , @exifpath )
+
+	if filter_mode == false then
+		url = ARGV.shift
+		if %r|http://([^:/]*):?(\d*)(/.*)| =~ url then
+			host = $1
+			port = $2.to_i
+			cgi = $3
+			raise 'posttdiary-ex: invalid url for update.rb.' if not host or not cgi
+			port = 80 if port == 0
+		else
+			raise 'posttdiary-ex: invalid url for update.rb.'
+		end
+		user = ARGV.shift
+		pass = ARGV.shift
+	end
+	
+	require 'base64'
+	require 'nkf'
+	require 'net/http'
+	require 'shell'
+	Net::HTTP.version_1_2
+
+	mail = NKF::nkf( '-m0 -wXd', ARGF.read )
+	raise "posttdiary-ex: no mail text." if not mail or mail.length == 0
+	
+	head, body = mail.split( /\r*\n\r*\n/, 2 )
+	body = "" unless body
+	addr, subject, tmpimglist, orglist, @body = parse_mail( head, body, image_dir )
+
+	if /([^-]+)-(.*)@/ =~ addr then
+		user = $1 unless user
+		pass = $2 unless pass
+	end
+	raise "posttdiary-ex: please specify the username for your tdiary system." unless user or filter_mode
+	if tmpimglist.length > 0 and ( !image_dir or !image_url ) then
+		raise "posttdiary-ex: please specify image-path (-i) and/or image-url (-u)"
+	end
+
+	now = Time::now + hour_offset * 3600
+	tmp = Time::now + hour_offset * 3600
+	if @body.gsub!( /^\_date\#([\d\-\/\.]+)[^\r\n]*[\r\n]+/i , '' ) then
+		t = $1
+		if /(\d\d\d\d)[^\d]*(\d\d)[^\d]*(\d\d)/ =~ t then
+			tmp = Time::local( $1.to_i, $2.to_i, $3.to_i );
+		end
+		if /(\d\d\d\d)[^\d]+(\d+)[^\d]+(\d+)/ =~ t then
+			tmp = Time::local( $1.to_i, $2.to_i, $3.to_i );
+		end
+	else
+		if blog_style and !filter_mode then
+			Net::HTTP.start( host, port ) do |http|
+				tmp = get_date_to_append( http, cgi, user, pass, now )
+			end
+		end
+	end
+	if @body.gsub!( /^\_up[ld]+only\#[ \t]*[\r\n]+/i , '' ) then
+		upload_only = true
+	end
+	if @body.gsub!( /^\_rot[ate]*\_right\#[ \t]*[\r\n]+/i , '' ) then
+		rotation_degree_specified = 90
+	end
+	if @body.gsub!( /^\_rot[ate]*\_left\#[ \t]*[\r\n]+/i , '' ) then
+		rotation_degree_specified = -90
+	end
+	if @body.gsub!( /^\_rot[ate]*\_none\#[ \t]*[\r\n]+/i , '' ) then
+		rotation_degree_specified = 0
+	end
+
+	if date_margin != 0 and (tmp - now).abs >= date_margin * 24 * 3600 then
+#		raise "posttdiary-ex: specified date is too far from today"
+#		# use current date (now) instead of specified date(tmp)..
+	else
+		now = tmp
+	end
+	
+	topic_year = now.strftime( "%Y" )
+	topic_month = now.strftime( "%m" )
+	topic_date = now.strftime( "%d" )
+
+	if image_dir then
+		image_dir = add_delimiter( image_dir + now.strftime( "%Y" ) ) if yearly_dir
+		Dir.mkdir( image_dir ) if !test( ?d , image_dir )
+		if remote_mode then
+			image_url += topic_year + "/" if remote_yearly_dir
+		else
+			image_url += topic_year + "/" if yearly_dir
+		end
+	end
+	nextnum = -1
+	av_list = []
+	if remote_mode then
+		if !remote_image_dir or remote_image_dir.length < 1 then
+#			needed when using image_ex.rb, but not when using image.rb...
+#			raise 'posttdiary-ex: please specify --remote-image-path'
+			remote_image_dir = ""
+		else
+			if remote_yearly_dir then
+				remote_image_dir = add_delimiter( remote_image_dir + topic_year )
+			end
+		end
+		Net::HTTP.start( host, port ) do |http|
+			nextnum,av_list = check_remote_images( http, cgi, user, pass, now)
+		end
+	else
+		nextnum,av_list = check_local_images( now.strftime( "%Y%m%d" ), image_dir )
+	end
+	@image_name = nil
+	sh = Shell.new
+	for i in 0 .. (tmpimglist.length-1)
+		tmpimgname = tmpimglist[i]
+		raise "posttdiary-ex: program bug found: no extension in tmpimgname" if !(tmpimgname =~ /(\.[^\.]*?)$/)
+		image_ext = $1.downcase
+		image_name = now.strftime( "%Y%m%d" ) + "_" + nextnum.to_s + image_ext
+		nextnum += 1
+		sh.move( tmpimgname , image_dir + image_name )
+		exif_comment[image_name] = (read_exif ? read_exif_comment(image_dir + image_name) : "" )
+		exif_orientation[image_name] = (read_exif ? read_exif_orientation(image_dir + image_name) : "" )
+		image_orgname[image_name] = orglist[i]
+		change_image_size( image_dir + image_name , image_geometry ) if image_geometry
+		if rotation_degree_specified then
+			if rotation_degree_specified != 0 then
+				rotate_image( image_dir + image_name , rotation_degree_specified )
+			end
+		elsif read_exif and exif_orientation[image_name] != 1 then
+			rotate_image( image_dir + image_name , rotation_degree( exif_orientation[image_name] ) )
+		end
+		if group_id then
+			sh.chown( nil , group_id , image_dir + image_name )
+			sh.chmod( 00664 , image_dir + image_name )
+		end
+		thumbnail_name[image_name] = ""
+		thumbnail_name[image_name] = make_thumbnail( image_dir, image_name , thumbnail_size , group_id ) if thumbnail_size and check_image_size( image_dir + image_name, threshold_size)
+		@image_name = [] unless @image_name
+		@image_name << image_name
+	end
+
+	if @image_name then
+		img_src = ""
+		marker = "_posttdiary_ex_temporary_marker_"
+		img_in_div = 0
+		for j in 0 .. @image_name.size-1
+			i = @image_name[j]
+			serial = i.sub( /^\d+_(\d+)\.[^\.]*?$/, '\1' )
+			serial = i if use_image_ex and pass_filename
+			cm = ""
+			cm = exif_comment[i] if read_exif and exif_comment[i] and exif_comment[i].size > 0
+			cm = image_orgname[i] if use_original_name and (!cm or cm.size == 0)
+			cm = i.gsub(/\.[^\.]*?$/, '') if !cm or cm.size == 0
+			if use_image_ex then
+				# modify <%=image (num),'comment'%> or <%=image (num)%> tags
+				if @body =~ /\<\%\=image[^\s]*\s+#{j}\s*\,\s*[\"\'](.*)[\"\']\s*\%*\>/i then
+					alttext = $1;
+					alttext = cm if alttext.length < 1
+					@body.gsub!( /\<\%\=(image[^\s]*)\s+#{j}\s*\,\s*[\"\'].*[\"\']\s*\%*\>/i, '<%=\1 '+marker+serial.to_s+',\''+alttext+'\'%>' )
+					next
+				elsif @body =~ /\<\%\=image[^\s]*\s+#{j}\s*\%*\>/i then
+					alttext = cm
+					@body.gsub!( /\<\%\=(image[^\s]*)\s+#{j}\s*\%*\>/i, '<%=\1 '+marker+serial.to_s+',\''+alttext+'\'%>' )
+					next
+				end
+			end
+			if wiki_style then
+				# modify {{image (num),"comment"}} or {{image (num)}} tags (also recognizes image_left, image_right)
+				if @body =~ /\{\{image[^\s]*\s+#{j}\s*\,\s*[\"\'](.*)[\"\']\s*\}\}/i then
+					alttext = $1;
+					alttext = cm if alttext.length < 1
+					@body.gsub!( /\{\{(image[^\s]*)\s+#{j}\s*\,\s*[\"\'].*[\"\']\s*\}\}/i, '{{\1 '+marker+serial.to_s+',\''+alttext+'\'}}' )
+					next
+				elsif @body =~ /\{\{image[^\s]*\s+#{j}\s*\}\}/i then
+					alttext = cm
+					@body.gsub!( /\{\{(image[^\s]*)\s+#{j}\s*\}\}/i, '{{\1 '+marker+serial.to_s+',\''+alttext+'\'}}' )
+					next
+				end
+			end
+			img_in_div+=1
+			if thumbnail_size and thumbnail_name[i].size > 0 then
+				t = thumbnail_name[i]
+				img_src += image_format_with_thumbnail.gsub( /\$0/, serial ).gsub( /\$1/, image_url + i ).gsub( /\$2/, image_url + t ).gsub( /\$3/, class_name ).gsub( /\$4/, cm )
+			else
+				img_src += image_format.gsub( /\$0/, serial ).gsub( /\$1/, image_url + i ).gsub( /\$3/, class_name ).gsub( /\$4/, cm )
+			end
+		end
+		@body.gsub!( /#{marker}/ , '' )
+		if img_src =~ /^\s+$/ then
+			img_src = ''
+		else
+			if add_div_imgnum <= img_in_div and add_div_imgnum > 0 then
+				img_src = "<div class=\"photos\">" + img_src + "</div>"
+			end
+		end
+		img_src.sub!( /^/ , ' ' ) if ! wiki_style
+		if use_subject then
+			img_src = img_src + "\n" if !(img_src =~ /^\s*$/)
+			@body = "#{img_src}#{@body.sub( /\n+\z/, '' )}"
+		else
+			@body = "#{@body.sub( /\n+\z/, '' )}\n#{img_src}"
+		end
+	end
+
+	if use_subject then
+		title = ''
+		@body = "#{subject}\n#{@body}"
+		@body = "!" + @body if wiki_style
+	else
+		title = subject
+	end
+
+	if upload_only then
+		exit 0
+	end
+	require 'cgi'
+	require 'nkf'
+	if filter_mode then
+		data = title + "\n";
+		data << @body + "\n";
+		if writeout_filename then
+			open( writeout_filename, "wb" ) do |s|
+				s.print data
+			end
+		else
+			print data
+		end
+	else
+		data = "title=#{CGI::escape title}"
+		data << "&body=#{CGI::escape @body}"
+		data << "&append=true"
+		data << "&year=#{topic_year}"
+		data << "&month=#{topic_month}"
+		data << "&day=#{topic_date}"
+		auth = ["#{user}:#{pass}"].pack( 'm' ).strip
+		Net::HTTP.start( host, port ) do |http|
+			protection_key = nil
+			res, = http.get( cgi,
+	                               'Authorization' => "Basic #{auth}",
+	                               'Referer' => url )
+			if %r|<input type="hidden" name="csrf_protection_key" value="([^"]+)">| =~ res.body then
+				protection_key = $1
+				data << "&csrf_protection_key=#{CGI::escape( CGI::unescapeHTML( protection_key ) )}"
+			end
+			if remote_mode and @image_name then
+				for i in 0 .. (@image_name.length - 1)
+					imagename = @image_name[i]
+					thumbnailname = thumbnail_name[imagename]
+					post_image( http, cgi, user, pass, image_dir, imagename, remote_image_dir, now, protection_key, url )
+					File.delete( image_dir + imagename ) if !preserve_local_images
+					File.delete( image_dir + thumbnailname ) if !preserve_local_images and test( ?f , image_dir + thumbnailname )
+				end
+			end
+			response, = http.post( cgi, data, 'Authorization' => "Basic #{auth}", 'Referer' => url )
+		end
+	end
+
+rescue
+	$stderr.puts $!
+	exit 1
+end
Index: /tdiary/branches/upstream/contrib/util/tdiarysearch/README.ja
===================================================================
--- /tdiary/branches/upstream/contrib/util/tdiarysearch/README.ja (revision 710)
+++ /tdiary/branches/upstream/contrib/util/tdiarysearch/README.ja (revision 710)
@@ -0,0 +1,92 @@
+tdiarysearch README
+===================
+
+  tDiary簡単検索CGIです。
+  あらかじめインデックスを作る必要がないので、
+  一般的な検索エンジンに比べてインストールが
+  圧倒的に簡単です。また、書いた直後の日記も
+  検索対象になるという長所があります。
+
+
+必要環境
+--------
+
+  * テキスト形式のデータベースを使っていること (tDiary 1.5 以降)
+  * tDiary が動いているサーバに独自の CGI を追加する権限があること
+
+
+セットアップ
+------------
+
+  1. search.rb を tDiary の index.rb の隣にコピーします。
+     必要なら index.rb と同じようにシンボリックリンクを
+     張ったり名前を変えたりしてください。
+
+  2. CGI として実行可能にします。
+
+     $ chmod a+x search.rb
+
+  3. 必要なら #! のパスを合わせます。
+
+  4. (オプション) search.rb 先頭のパラメータを変更して
+     クエリーログを有効にしてください。
+     ※ クエリーログの詳細については後述
+
+  5. 自分の tDiary の好きな場所 (例えばヘッダ) に以下の
+     フォームを加えてください。
+
+  <form method="post" action="search.rb" class="searchform">
+  <input type="text" name="q" size="20" value="">
+  <input type="submit" value="Search">
+  </form>
+
+  以上です。
+
+
+検索のしかた
+------------
+
+  tdiarysearch はトピックごとに AND 検索を行います。
+  指定した語は空白で分割され、全単語がトピック内のテキストに
+  全て含まれる場合にそのトピックを検索結果にリストします。
+
+  AND 以外の演算はサポートしません。
+
+  また rev 1.8 の段階ではまだツッコミが検索できません。
+  近いうちに追加します。
+
+
+クエリーログ
+------------
+
+  クエリーログ (query log) というのは、検索語のログの
+  ことです。このログが存在すると他の人が何を検索したのか
+  見られるようになります。クエリーログを有効にするには、
+  search.rb 内の定数 LOGGING を true にしてください。
+
+  CGI からログを見られるようにするには、search.rb に
+  パラメータ history=on を付けて呼びます。例えば
+  次のリンクを日記に入れておくと、リンクをたどるだけで
+  日記から最近の検索ログが見られます。
+
+    <a href="search.rb?history=on">[検索ログ]</a>
+
+  なお、クエリーログは tDiary のデータディレクトリに search.log
+  という名前で作成されます。特にローテーション処理はしていないので、
+  適当な間隔でローテートしてください。
+
+
+連絡先
+------
+
+  青木峰郎 (あおき・みねろう)
+  Minero Aoki <aamine@loveruby.net>
+  http://i.loveruby.net/w/tdiarysearch.html
+
+  バグ報告は以下のどこかにお願いします。
+
+    * Wiki 上のバグ報告ページ
+      http://i.loveruby.net/w/tdiarysearchBugs.html
+    * tdiary-devel ML
+    * 直接メール
+
Index: /tdiary/branches/upstream/contrib/util/tdiarysearch/search.rb
===================================================================
--- /tdiary/branches/upstream/contrib/util/tdiarysearch/search.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/util/tdiarysearch/search.rb (revision 710)
@@ -0,0 +1,479 @@
+#!/usr/bin/env ruby
+#
+# tdiarysearch
+#
+# Copyright (C) 2003-2005 Minero Aoki
+#
+# This program is free software.
+# You can distribute/modify this program under the terms of
+# the GNU GPL, General Public License version 2.
+#
+# $originalId: search.rb,v 1.14 2005/07/27 07:16:07 aamine Exp $
+#
+# Project home page: http://i.loveruby.net/w/tdiarysearch.html
+#
+
+#
+# Static Configurations
+#
+
+LOGGING = false
+LOGFILE_NAME = 'search.log'
+DEBUG = $DEBUG
+
+#
+# HTML Templates
+#
+
+def unindent(str)
+  str.gsub(/^#{str[/\A(?:\t+| +)/]}/, '')
+end
+
+HEADER = unindent <<'EOS'
+  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+  <html lang="ja">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <meta http-equiv="Content-Language" content="ja">
+    <meta name="robots" content="noindex">
+    <link rel="stylesheet" href="theme/base.css" type="text/css" media="all">
+    <link rel="stylesheet" href="theme/<%= theme %>/<%= theme %>.css" title="<%= theme %>" type="text/css" media="all">
+    <title>tDiary Search</title>
+  </head>
+  <body>
+EOS
+
+FOOTER = unindent <<'EOS'
+  </body>
+  </html>
+EOS
+
+SEARCH_FORM = unindent <<"EOS"
+  <form method="post" action="#{File.basename(__FILE__)}">
+  <input type="text" name="q" size="20" value="<%= patterns.map {|re| escape(re.source) }.join(' ') %>">
+  <input type="submit" value="Search">
+  <%
+      if theme
+  %><input type="hidden" name="theme" value="on"><%
+      end
+  %>
+  </form>
+EOS
+
+SEARCH_PAGE = unindent <<"EOS"
+  <h1>tDiary Search</h1>
+  #{SEARCH_FORM}
+EOS
+
+TOO_MANY_HITS = 50
+
+SEARCH_RESULT = unindent <<"EOS"
+  <h1>tDiary Search: Search Result</h1>
+  #{SEARCH_FORM}
+  <%
+      nhits = 0
+      toomanyhits = false
+      match_components(patterns) {|diary, fragment, component|
+        nhits += 1
+        if nhits > TOO_MANY_HITS
+          toomanyhits = true
+          break
+        end
+  %>
+  <div class="day">
+  <h2><a href="<%= @config.index %>?date=<%= diary.ymd %>#<%= fragment %>"><%= diary.y_m_d %></a></h2>
+  <div class="body">
+  <div class="section">
+  <p><%= short_html(component) %></p>
+  </div>
+  </div>
+  </div><%
+      }
+  %>
+  <p><%= toomanyhits ? 'too many hits.' : nhits.to_s+' hits.' %></p>
+  #{SEARCH_FORM}
+EOS
+
+SEARCH_ERROR = unindent <<"EOS"
+  #{SEARCH_FORM}
+  <%= escape(reason) %>.
+EOS
+
+HISTORY = unindent <<"EOS"
+  <h1>tDiary Search: Search History</h1>
+  #{SEARCH_FORM}
+  <ul>
+  <%
+      recent_queries.sort_by {|t,q| -t.to_i }.each do |time, query|
+  %><li><%= time.strftime('%Y-%m-%d %H:%M:%S') %> | <a href="#{File.basename(__FILE__)}?q=<%= escape_url(query) %>"><%= escape(query) %></a></li>
+  <%
+      end
+  %></ul>
+  #{SEARCH_FORM}
+EOS
+
+#
+# Main
+#
+
+if File.symlink?(__FILE__)
+  tdiarylib = File.dirname(File.readlink(__FILE__))
+else
+  tdiarylib = File.dirname(__FILE__)
+end
+$:.unshift tdiarylib
+require 'tdiary'
+require 'tdiary/defaultio'
+require 'erb'
+
+class WrongQuery < StandardError; end
+
+Z_SPACE = "\241\241"   # zen-kaku space
+
+BEGIN { $defout.binmode }
+
+def main
+  $KCODE = 'u'
+  cgi = CGI.new
+  @config = TDiary::Config.new(cgi)
+  @config.options['apply_plugin'] = true
+  html = '<html><head><title></title></head><body><p>error</p></body></html>'
+  begin
+    html = generate_page(cgi)
+  ensure
+    send_html cgi, html
+  end
+  exit 0
+end
+
+def generate_page(cgi)
+  query = nil
+  begin
+    theme = @config.theme
+    if LOGGING and File.file?(query_log()) and cgi.valid?('history')
+      return history_page(theme)
+    end
+    begin
+      return search_form_page(theme) unless cgi.valid?('q')
+      initialize_tdiary_plugins cgi
+      query = @config.to_native([cgi.params['q']].flatten.compact.join(' '))
+      patterns = setup_patterns(query)
+      html = search_result_page(theme, patterns)
+      save_query(query, query_log()) if LOGGING
+      return html
+    rescue WrongQuery => err
+      return search_error_page(theme, (patterns || []), err.message)
+    end
+  rescue Exception => err
+    html = ''
+    html << HEADER
+    html << "<pre>\n"
+    html << 'q=' << escape(query) << "\n" if query
+    html << escape(err.class.name) << "\n" if DEBUG
+    html << escape(err.message) << "\n"
+    html << err.backtrace.map {|i| escape(i) }.join("\n") if DEBUG
+    html << "</pre>\n"
+    html << FOOTER
+    return html
+  end
+end
+
+def send_html(cgi, html)
+  print cgi.header('status' => '200 OK',
+                   'type' => 'text/html',
+                   'charset' => 'utf-8',
+                   'Content-Length' => html.length.to_s,
+                   'Cache-Control' => 'no-cache',
+                   'Pragma' => 'no-cache')
+  print html unless cgi.request_method == 'HEAD'
+end
+
+def setup_patterns(query)
+  patterns = split_string(query).map {|pat|
+    check_pattern pat
+    /#{Regexp.quote(pat)}/ie
+  }
+  raise WrongQuery, 'no pattern' if patterns.empty?
+  raise WrongQuery, 'too many sub patterns' if patterns.length > 8
+  patterns
+end
+
+def check_pattern(pat)
+  raise WrongQuery, 'no pattern' unless pat
+  raise WrongQuery, 'empty pattern' if pat.empty?
+  raise WrongQuery, "pattern too short: #{pat}" if pat.length < 2
+  raise WrongQuery, 'pattern too long' if pat.length > 128
+end
+
+def split_string(str)
+  str.split(/[\s#{Z_SPACE}]+/oe).reject {|w| w.empty? }
+end
+
+def save_query(query, file)
+  File.open(file, 'a') {|f|
+    begin
+      f.flock(File::LOCK_EX)
+      f.puts "#{Time.now.to_i}: #{query.dump}"
+    ensure
+      f.flock(File::LOCK_UN)
+    end
+  }
+end
+
+#
+# eRuby Dispatchers and Helper Routines
+#
+
+def search_form_page(theme)
+  patterns = []
+  ERB.new(HEADER + SEARCH_FORM + FOOTER).result(binding())
+end
+
+def search_result_page(theme, patterns)
+  ERB.new(HEADER + SEARCH_RESULT + FOOTER).result(binding())
+end
+
+def search_error_page(theme, patterns, reason)
+  ERB.new(HEADER + SEARCH_ERROR + FOOTER).result(binding())
+end
+
+def history_page(theme)
+  patterns = []
+  ERB.new(HEADER + HISTORY + FOOTER).result(binding())
+end
+
+def query_log
+  "#{@config.data_path}#{LOGFILE_NAME}"
+end
+
+N_SHOW_QUERY_MAX = 20
+
+def recent_queries
+  return unless File.file?(query_log())
+  File.readlines(query_log()).reverse[0, N_SHOW_QUERY_MAX].map {|line|
+    time, q = *line.split(/:/, 2)
+    [Time.at(time.to_i), eval(q)]
+  }
+end
+
+INF = 1 / 0.0
+
+def match_components(patterns)
+  foreach_diary_from_latest do |diary|
+    next unless diary.visible?
+    num = 1
+    diary.each_section do |sec|
+      if patterns.all? {|re| re =~ sec.to_src }
+        yield diary, fragment('p', num), sec
+      end
+      num += 1
+    end
+    diary.each_visible_comment(INF) do |cmt, num|
+      if patterns.all? {|re| re =~ cmt.body }
+        yield diary, fragment('c', num), cmt
+      end
+    end
+  end
+end
+
+def fragment(type, num)
+  sprintf('%s%02d', type, num)
+end
+
+#
+# tDiary Implementation Dependent
+#
+
+def foreach_diary_from_latest(&block)
+  foreach_data_file(@config.data_path.sub(%r</+\z>, '')) do |path|
+    read_diaries(path).sort_by {|diary| diary.date }.reverse_each(&block)
+  end
+end
+
+def foreach_data_file(data_path, &block)
+  Dir.glob("#{data_path}/[0-9]*/*.td2").sort.reverse_each do |path|
+    yield path.untaint
+  end
+end
+
+def read_diaries(path)
+  d = nil
+  diaries = {}
+  load_tdiary_textdb(path) do |header, body|
+    d = diary_class(header['Format']).new(header['Date'], '', body)
+    d.show(header['Visible'] != 'false')
+    diaries[d.ymd] = d
+  end
+  (Years[d.y] ||= []).push(d.m) if d
+  load_comments diaries, path
+  diaries.values
+end
+
+DIARY_CLASS_CACHE = {}
+
+def diary_class(style)
+  c = DIARY_CLASS_CACHE[style]
+  return c if c
+  require "tdiary/#{style.downcase}_style.rb"
+  c = eval("TDiary::#{style.capitalize}Diary")
+  c.__send__(:include, DiaryClassDelta)
+  DIARY_CLASS_CACHE[style] = c
+  c
+end
+
+module DiaryClassDelta
+  def ymd
+    date().strftime('%Y%m%d')
+  end
+
+  def y_m_d
+    date().strftime('%Y-%m-%d')
+  end
+
+  def y
+    '%04d' % date().year
+  end
+
+  def m
+    '%02d' % date().month
+  end
+end
+
+def load_comments(diaries, path)
+  cmtfile = path.sub(/2\z/, 'c')
+  return unless File.file?(cmtfile)
+  load_tdiary_textdb(cmtfile) do |header, body|
+    c = TDiary::Comment.new(header['Name'], header['Mail'], body,
+                            Time.at(header['Last-Modified'].to_i))
+    c.show = (header['Visible'] != 'false')
+    d = diaries[header['Date']]
+    d.add_comment c if d
+  end
+end
+
+def load_tdiary_textdb(path)
+  File.open(path) {|f|
+    ver = f.gets.strip
+    raise "unkwnown format: #{ver}" unless ver == 'TDIARY2.00.00'
+    f.each('') do |header|
+      h = {}
+      header.untaint.strip.each do |line|
+        n, v = *line.split(':', 2)
+        h[n.strip] = v.strip
+      end
+      yield h, f.gets("\n.\n").chomp(".\n").untaint
+    end
+  }
+end
+
+def short_html(component)
+  # Section classes do not have common superclass, we can't use class here.
+  case component.class.name
+  when /Section/
+    section = component
+    if section.subtitle
+      sprintf('%s<br>%s',
+              tdiary2text(section.subtitle_to_html),
+              tdiary2text(section.body_to_html))
+    else
+      tdiary2text(section.body_to_html)
+    end
+  when /Comment/
+    cmt = component
+    escape((cmt.name + ': ' + cmt.body).slice(/\A.{0,120}/me))
+  else
+    raise "must not happen: #{component.class}"
+  end
+end
+
+def tdiary2text(html)
+  apply_tdiary_plugins(html).gsub(%r[<[^>]*>]em, '').slice(/\A.{0,120}/me)
+end
+
+Years = {}
+
+TDiary::Plugin.__send__(:public, :apply_plugin)
+def apply_tdiary_plugins(html)
+  @plugin.apply_plugin(html, false)
+end
+
+@plugin = nil
+
+def initialize_tdiary_plugins(cgi)
+  @plugin = TDiary::Plugin.new('conf' => @config,
+                               'mode' => 'month',
+                               'secure' => false,
+                               'diaries' => {},
+                               'cgi' => cgi,
+                               'index' => @config.index,
+                               'years' => Years,
+                               'cache_path' => @config.cache_path ||
+                                               @config.data_path)
+end
+
+#
+# Utils
+#
+
+HTML_ESCAPE_TABLE = {
+  '&' => '&amp;',
+  '<' => '&lt;',
+  '>' => '&gt;',
+  '"' => '&quot;'
+}
+
+def escape(str)
+  tbl = HTML_ESCAPE_TABLE
+  str.gsub(/[&"<>]/) {|ch| tbl[ch] }
+end
+
+def escape_url(u)
+  escape(urlencode(u))
+end
+
+def urlencode(str)
+  str.gsub(/[^\w-]/n) {|ch| sprintf('%%%02x', ch[0]) }
+end
+
+#
+# Old Ruby Compatibility
+#
+
+if RUBY_VERSION < '1.8.0'
+  class String
+    remove_method :slice
+    def slice(re, n = 0)
+      m = re.match(self) or return nil
+      m[n]
+    end
+  end
+end
+
+unless Array.method_defined?(:all?)
+  module Enumerable
+    def all?
+      each do |i|
+        return false unless yield(i)
+      end
+      true
+    end
+  end
+end
+
+unless Array.method_defined?(:sort_by)
+  module Enumerable
+    def sort_by
+      map {|i| [yield(i), i] }.sort.map {|val, i| i }
+    end
+  end
+end
+
+unless MatchData.method_defined?(:captures)
+  class MatchData
+    def captures
+      to_a()[1..-1]
+    end
+  end
+end
+
+main
Index: /tdiary/branches/upstream/contrib/util/tdiarysearch/README.en
===================================================================
--- /tdiary/branches/upstream/contrib/util/tdiarysearch/README.en (revision 710)
+++ /tdiary/branches/upstream/contrib/util/tdiarysearch/README.en (revision 710)
@@ -0,0 +1,48 @@
+tdiarysearch README
+===================
+
+This is tdiarysearch, simple tDiary search interface.
+
+
+Installation and setup
+----------------------
+
+  1. Copy search.rb aside of tDiary's index.rb.
+
+    $ ls
+    index.rb*
+    search.rb*
+    tdiary.conf
+    update.rb*
+
+  2. Make search.rb executable.
+
+    $ chmod a+x search.rb
+  
+  3. (Optional) Adjust shebang (#!) line of search.rb.
+
+  4. (Optional) If you'd like, edit embedded paramter in
+     search.rb and turn on query logging.
+  
+  5. Add following HTML form in your tDiary header:
+
+    <form method="post" action="search.rb" class="searchform">
+    <input type="text" name="q" size="20" value="">
+    <input type="submit" value="Search">
+    </form>
+
+  6. Done.
+
+
+License
+-------
+
+  GNU GPL, General Public License version 2.
+
+
+Contact
+-------
+
+  Minero Aoki <aamine@loveruby.net>
+  http://i.loveruby.net/en/
+
Index: /tdiary/branches/upstream/contrib/util/tdiary-vim/tdiary.vim
===================================================================
--- /tdiary/branches/upstream/contrib/util/tdiary-vim/tdiary.vim (revision 710)
+++ /tdiary/branches/upstream/contrib/util/tdiary-vim/tdiary.vim (revision 710)
@@ -0,0 +1,351 @@
+" Copyright (C) 2004 UECHI Yasumasa
+
+" Author: UECHI Yasumasa <uechi@potaway.net>
+
+" $Revision: 1.8 $
+
+" This program is free software; you can redistribute it and/or
+" modify it under the terms of the GNU General Public License as
+" published by the Free Software Foundation; either version 2, or (at
+" your option) any later version.
+
+" This program is distributed in the hope that it will be useful, but
+" WITHOUT ANY WARRANTY; without even the implied warranty of
+" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
+" General Public License for more details.
+
+" You should have received a copy of the GNU General Public License
+" along with this program; see the file COPYING.  If not, write to the
+" Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+" Boston, MA 02111-1307, USA.
+
+
+if !exists("g:tdiary_site1_url")
+	finish
+endif
+
+command! -nargs=0 TDiaryNew call <SID>TDiaryNew()
+command! -nargs=0 TDiaryReplace call <SID>TDiaryReplace()
+command! -nargs=0 TDiaryUpdate call <SID>TDiaryUpdate()
+command! -nargs=0 TDiarySelect call <SID>TDiarySelect()
+command! -nargs=0 TDiaryTrackback call <SID>EditTrackBackExcerpt()
+
+let s:curl_cmd = "curl"
+let s:user = ''
+
+function! s:TDiaryNew()
+	call s:CreateBuffer("append")
+	execute ":" . (s:body_start + 1)
+	normal dG
+	redraw!
+endfunction
+
+function! s:TDiaryReplace()
+	call s:CreateBuffer("replace")
+
+	let save_pat = @/
+	let @/ = 'input.\+name="title"[^>]\+>'
+	normal ggn
+	let title = substitute(getline("."), '.\+value="\(.*\)".\+', '\1', '')
+
+	let @/ = 'textarea \+name="body"[^>]\+>'
+	execute ":" . s:body_start
+	normal dndf>
+	let @/ = '</textarea'
+	normal ndG
+	silent! %s/
+//
+
+	silent! %s/&quot;/\"/g
+	silent! %s/&gt;/>/g
+	silent! %s/&lt;/</g
+	silent! %s/&amp;/\&/g
+
+	normal gg
+	let @/ = '^Title:'
+	normal n
+	execute "normal A" . title . "\<Esc>"
+
+	normal G
+	redraw!
+	let @/ = save_pat
+endfunction
+
+function! s:TDiaryUpdate()
+	" move to _tdiary_ buffer
+	let n = bufwinnr(substitute(bufname("%"), "_.\\+_", "_tdiary_", ""))
+	execute "normal " . n . "\<C-W>w"
+	
+	" set parameters
+	let data = s:SetParams()
+
+	" set body & csrf protection key
+	let data = data . "&body=" . s:MultiLineURLencode(s:body_start)
+	let data = data . s:csrf_protection_key
+
+	" debug mode
+	if exists("g:tdiary_vim_debug") && g:tdiary_vim_debug
+		call append("$", data)
+		return
+	endif
+
+	" redirect data to tmpfile
+	let tmpfile = tempname()
+	execute "redir! > " . tmpfile
+	silent echo data
+	redir END
+
+	" update diary
+	let result = system(s:curl_cmd . s:user . " -d @" . tmpfile . " -e ". s:tdiary_update_url . " " . s:tdiary_update_url)
+	call delete(tmpfile)
+	redraw!
+	if match(result, 'Wait or.\+Click here') != -1
+		echo "SUCCESS"
+	else
+		echo result
+	endif
+endfunction
+
+
+function! s:TDiarySelect()
+	split tDiary_select
+	set buftype=nofile
+	set nobuflisted
+	set noswapfile
+
+	let i = 1
+	while exists("g:tdiary_site{i}_url")
+		let site_name = ''
+		if exists("g:tdiary_site{i}_name")
+			let site_name = g:tdiary_site{i}_name . " "
+		endif
+		call append(i - 1, site_name . g:tdiary_site{i}_url)
+		let i = i + 1
+	endwhile
+	normal gg
+
+	nnoremap <buffer> <silent> <CR> :call <SID>SetURL()<CR>
+endfunction
+
+
+function! s:EditTrackBackExcerpt()
+	let save_line = line(".")
+	normal gg
+	call search("^TrackBackURL:")
+	let tb_url = s:ParamValue(getline("."))
+
+	let tb_url = input("TrackBackURL: ", tb_url)
+	delete
+	call append(line(".") - 1, "TrackBackURL: " . tb_url)
+	execute ":" . save_line
+
+	let tb_bufname = substitute(bufname("%"), "_tdiary_", "_trackback_", "")
+	split
+	execute "normal \<C-W>w"
+	execute "edit " . tb_bufname
+	set buftype=nofile
+	set noswapfile
+	set bufhidden=hide
+endfunction
+
+
+function! s:SetParams()
+	let data = ''
+	let i = 1
+
+	while i < s:body_start
+		let l = getline(i)
+		let r = s:ParamValue(l)
+
+		if l =~ "^Editing mode"
+			let mode = r
+			let data = data . "&" . r . "=" . r
+		elseif l =~ "^Date:"
+			let data = data . s:Date2PostDate(r, mode)
+		elseif l =~ "^Title:"
+			let data = data . "&title=" . s:URLencode(r)
+		elseif l =~ "^TrackBackURL:"
+			if r != ""
+				let data = data . "&plugin_tb_url=" . s:URLencode(r)
+				let data = data . s:TrackBackExcerpt()
+			endif
+		endif
+			
+		let i = i + 1
+	endwhile
+
+	return data
+endfunction
+
+
+function! s:ParamValue(str)
+	let r = substitute(a:str, '^[^:]\+ *: *\(.*\)', '\1', '')
+	let r = substitute(r, ' *$', '', '')
+	return r
+endfunction
+
+
+function! s:TrackBackExcerpt()
+	let data = "&plugin_tb_excerpt="
+	let n = bufwinnr(substitute(bufname("%"), "_.\\+_", "_trackback_", ""))
+	if n > 0
+		execute "normal " . n . "\<C-W>w"
+		let data = data . s:MultiLineURLencode(1)
+		execute "normal \<C-W>p"
+	endif
+	return data
+endfunction
+
+
+function! s:MultiLineURLencode(start_line)
+	let i = a:start_line
+	let lastline = line("$")
+	let data = ""
+
+	while i <= lastline
+		let data = data . s:URLencode(getline(i) . "\r\n")
+		let i = i + 1
+	endwhile
+
+	return data
+endfunction
+
+
+function! s:SetURL(...)
+	if a:0 == 0
+		let i = line(".")
+	else
+		let i = a:1
+	endif
+	let s:tdiary_url = substitute(g:tdiary_site{i}_url, "/\\+$", "", "") . "/"
+	if exists("g:tdiary_site{i}_updatescript")
+		let update_script = g:tdiary_site{i}_updatescript
+	elseif exists("g:tdiary_update_script_name")
+		let update_script = g:tdiary_update_script_name
+	else
+		let update_script = "update.rb"
+	endif
+	let s:tdiary_update_url = s:tdiary_url . update_script
+
+	let s:user = ""
+	call s:SetUser()
+
+	"echo selected site
+	let site_name = ""
+	if exists("g:tdiary_site{i}_name")
+		let site_name = g:tdiary_site{i}_name
+	endif
+	echo site_name s:tdiary_url
+
+	if a:0 == 0
+		close
+	endif
+endfunction
+
+
+function! s:SetUser()
+	if exists("g:tdiary_use_netrc") && g:tdiary_use_netrc
+		let s:user = " --netrc "
+	elseif s:user == ''
+		let s:user = input("User Name: ")
+		let password = inputsecret("Password: ")
+		if s:user != ''
+			let  s:user = " -u '" . s:user . ":" . password . "' "
+		endif
+	endif
+endfunction
+
+
+function! s:CreateBuffer(mode)
+	if !exists("s:tdiary_update_url")
+		call s:SetURL(1)
+	endif
+
+	let date = input("Date: ", strftime("%Y%m%d", localtime()))
+	execute "edit _tdiary_" . date
+	set buftype=nofile
+	set noswapfile
+	set bufhidden=hide
+	"set fileformat=dos
+
+	let s:body_start = 0
+	
+	call append(s:body_start, "Editing mode (append or replace): " . a:mode)
+	let s:body_start = s:body_start + 1
+	
+	call append(s:body_start, "TrackBackURL: ")
+	let s:body_start = s:body_start + 1
+
+	call append(s:body_start, "Date: " . date)
+	let s:body_start = s:body_start + 1
+
+	call append(s:body_start, "Title: ")
+	let s:body_start = s:body_start + 1
+
+	let s:body_start = s:body_start + 1
+
+
+	let data = ""
+	if a:mode == "replace"
+		let data = ' -d "'
+		let data = data . s:Date2PostDate(date, a:mode)
+		let data = data . '&edit=edit" '
+	endif
+	execute 'r !' . s:curl_cmd . ' -s ' . s:user . data . s:tdiary_update_url
+
+	normal gg
+	let s:csrf_protection_key = ""
+	if search('input.\+name="csrf_protection_key"') > 0
+		silent! s/&quot;/\"/g
+		silent! s/&gt;/>/g
+		silent! s/&lt;/</g
+		silent! s/&amp;/\&/g
+		let k = substitute(getline("."), '.\+value="\(.*\)".\+', '\1', '')
+		let s:csrf_protection_key = "&csrf_protection_key=" . s:URLencode(k)
+	endif
+endfunction
+
+
+function! s:Date2PostDate(date, mode)
+	let year = strpart(a:date, 0, 4)
+	let month = strpart(a:date, 4, 2)
+	let day = strpart(a:date, 6, 2)
+
+	let old = ''
+	if a:mode == "replace"
+		let old = "&old=" . a:date
+	endif
+
+	return  "&year=" . year . "&month=" . month . "&day=" . day . old
+endfunction
+
+
+function! s:URLencode(str)
+	let r = iconv(a:str, &encoding, 'euc-jp')
+	let save_enc = &encoding
+	let &encoding = 'japan'
+	let r = substitute(r, '[^ a-zA-Z0-9_.-]', '\=s:Char2Hex(submatch(0))', 'g')
+	let &encoding = save_enc
+	let r = substitute(r, ' ', '+', 'g')
+	return r
+endfunction
+
+
+function! s:Char2Hex(c)
+	let n = char2nr(a:c)
+	let r = ''
+
+	while n
+		let r = '0123456789ABCDEF'[n % 16] . r
+		let n = n / 16
+	endwhile
+
+	if strlen(r) % 2 == 1
+		let r = '0' . r
+	endif
+
+	let r = substitute(r, '..', '%\0', 'g')
+
+	return r
+endfunction
+
Index: /tdiary/branches/upstream/contrib/util/tdiary-vim/ChangeLog
===================================================================
--- /tdiary/branches/upstream/contrib/util/tdiary-vim/ChangeLog (revision 710)
+++ /tdiary/branches/upstream/contrib/util/tdiary-vim/ChangeLog (revision 710)
@@ -0,0 +1,25 @@
+2005-07-29 Fri UECHI Yasumasa <uechi@potaway.net>
+	* echo site name and url when select a diary site.
+	* allow to set update script each diary.
+	* use CSRF protection key if exits.
+
+2005-07-28 Thu UECHI Yasumasa <uechi@potaway.net>
+	* send referer when update a diary.
+
+2004-11-08 Mon UECHI Yasumasa <uechi@potaway.net>
+	* change substitution order of character entity in TdiaryReplace(). thanx やえもん.
+
+2004-02-25 Wed UECHI Yasumasa <uechi@potaway.net>
+	* support TrackBack
+
+2004-02-19 Thu UECHI Yasumasa <uechi@potaway.net>
+	* quote argument of curl's -u option.
+
+2004-02-17 Tue UECHI Yasumasa <uechi@potaway.net>
+	* use .netrc when g:tdiary_use_netrc = 1
+
+2004-02-16 Mon UECHI Yasumasa <uechi@potaway.net>
+	* change editing mode, date, title in diary buffer.
+
+2004-02-13 Fri UECHI Yasumasa <uechi@potaway.net>
+	* commit to CVS repository
Index: /tdiary/branches/upstream/contrib/util/tdiary-vim/README
===================================================================
--- /tdiary/branches/upstream/contrib/util/tdiary-vim/README (revision 710)
+++ /tdiary/branches/upstream/contrib/util/tdiary-vim/README (revision 710)
@@ -0,0 +1,60 @@
+= tdiary.vim
+tdiary.vim は vim から日記を更新するための vimスクリプトです。
+
+== 必要なもの
+tdiary.vim を使用するには以下のものが必要です。
+
+* cURL - コマンドラインから http や ftp で通信するためのツール
+* iconv ライブラリ
+* vim (+iconv でコンパイルしたもの)
+
+cURL は ((<URL:http://curl.haxx.se/>)) から入手できます。
+
+== インストール
+tdiary.vim を $HOME/.vim/plugin ディレクトリにコピーするだけです。
+
+== 設定
+$HOME/.vimrc で次のように tDiary サイトの URL を設定します。
+
+ let tdiary_site1_url = "http://www.example.com/tdiary/"
+
+最低限必要な設定はこれだけですが、複数の日記を更新するなら以下のような設定を追加してください。
+
+ let tdiary_site1_name = "ほげほげ日記"
+ let tdiary_site2_url = "http://www.example.net/tdiary/"
+ let tdiary_site2_name = "foobar日記"
+
+tDiary を設置するときに update.rb を update.cgi 等に変更している場合は以下も追加してください。
+
+ let tdiary_site1_updatescript = "update.cgi"
+ let tdiary_site2_updatescript = "update.cgi"
+
+== パスワードの保存
+cURL は $HOME/.netrc を読めるので、パスワードの保存にその機能を使います。ユーザ名とパスワードを .netrc に保存し、$HOME/.vimrc に
+
+ let tdiary_use_netrc = 1
+
+と設定すると有効になります。
+
+== 使い方
+tdiary.vim では次のコマンドが使えます。
+
+* :TDiaryNew - 日記を書くための空のバッファを開きます
+* :TDiaryReplace - 指定した日付の日記を読み込んだバッファを開きます
+* :TDiaryUpdate - 日記を更新します
+* :TDiarySelect - 更新する tDiary サイトを選択するためのバッファを開きます
+* :TDiaryTrackbak - TrackBack の excerpt を編修するためのバッファを開きます
+
+=== 日記に追記
+:TDiaryNew すると日付が YYYYMMDD 形式で表示されますので、適宜修正してリターンキーを押してください。日記を書いたら :TDiaryUpdate で更新してください。
+
+=== 日記を修正
+:TDiaryReplace すると日付が YYYYMMDD 形式で表示されますので、修正したい日付に直してリターンキーを押してください。指定した日の日記が読み込まれます。修正後に :TDiaryUpdate で更新してください。
+
+=== tDiary サイトの選択
+:TDiarySelect すると日記の一覧が表示されます。日記にカーソルを合わせてリターンキーを押すことで書き込み先が切り替わります。
+
+=== TrackBack
+日記の編修バッファに表示されている TrackBackURL: に TrackBack 先の URL を記
+入することで TrackBack が送られます。excerpt を編修する場合は :TDiaryTrackback
+することで編修用のバッファが開きます。
Index: /tdiary/branches/upstream/contrib/util/tdiary-for-ruby1.9/compatible.rb
===================================================================
--- /tdiary/branches/upstream/contrib/util/tdiary-for-ruby1.9/compatible.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/util/tdiary-for-ruby1.9/compatible.rb (revision 710)
@@ -0,0 +1,97 @@
+# = for Ruby1.9.0 compatible =
+#
+# == 前提条件 ==
+#
+#  * Ruby1.9 の場合は --encoding=Binary オプションで動作させること
+
+# --------------------------------------------------------
+# 汎用的な設定
+# --------------------------------------------------------
+
+# for Ruby1.9.0
+
+unless "".respond_to?('to_a')
+  class String
+    def to_a
+      [ self ]
+    end
+  end
+end
+
+unless "".respond_to?('each')
+  class String
+    alias each each_line
+  end
+end
+
+# Ruby1.9では String が Enumerable ではなくなった
+class String
+  def method_missing(name, *args, &block)
+    each_line.__send__(name, *args, &block)
+  end
+end
+
+
+# for Ruby1.8.X
+
+unless "".respond_to?('force_encoding')
+  class String
+    def force_encoding(encoding)
+      self
+    end
+  end
+end
+
+unless "".respond_to?('bytesize')
+  class String
+    alias bytesize size
+  end
+end
+
+unless "".respond_to?('ord')
+  class String
+    def ord
+      self[0]
+    end
+  end
+
+  class Integer
+    def ord
+      self
+    end
+  end
+end
+
+# --------------------------------------------------------
+# tDiary 用の設定
+# --------------------------------------------------------
+
+# Ruby1.9でNKF::nkfを呼ぶと文字列のencodingが変わってしまう。
+# そのため、encodingがBinaryの環境で動かすと
+# "character encodings differ" エラーとなる。
+begin
+  require 'nkf'
+  module NKF
+    alias :_nkf :nkf
+    def nkf(option, src)
+      r = _nkf(option, src)
+      r.force_encoding('Binary')
+    end
+    module_function :nkf, :_nkf
+  end
+rescue
+end
+
+# 日本語を含むツッコミを入れると diary.last_modified が String になる (原因不明)
+# (PStore 保存前は Time だが, 保存後に String となる)
+# 暫定的に String だったら Time へ変換する
+module TDiary
+  class WikiDiary
+    def last_modified
+      if @last_modified.instance_of? String
+        @last_modified = Time.at(0)
+      end
+      @last_modified
+    end
+  end
+end
Index: /tdiary/branches/upstream/contrib/util/tdiary-for-ruby1.9/tdiary-2.2.0.patch
===================================================================
--- /tdiary/branches/upstream/contrib/util/tdiary-for-ruby1.9/tdiary-2.2.0.patch (revision 710)
+++ /tdiary/branches/upstream/contrib/util/tdiary-for-ruby1.9/tdiary-2.2.0.patch (revision 710)
@@ -0,0 +1,98 @@
+Index: update.rb
+===================================================================
+--- update.rb	(revision 97)
++++ update.rb	(working copy)
+@@ -5,7 +5,7 @@
+ # Copyright (C) 2001-2003, TADA Tadashi <sho@spc.gr.jp>
+ # You can redistribute it and/or modify it under GPL2.
+ #
+-BEGIN { $defout.binmode }
++BEGIN { $stdout.binmode }
+ $KCODE = 'n'
+ 
+ begin
+Index: misc/lib/hikidoc.rb
+===================================================================
+--- misc/lib/hikidoc.rb	(revision 97)
++++ misc/lib/hikidoc.rb	(working copy)
+@@ -476,7 +476,7 @@
+ 
+   def escape_meta_char( text )
+     text.gsub( META_CHAR_RE ) do |s|
+-      '&#x%x;' % s[1]
++      '&#x%x;' % s[1].ord
+     end
+   end
+ 
+Index: misc/plugin/amazon.rb
+===================================================================
+--- misc/plugin/amazon.rb	(revision 97)
++++ misc/plugin/amazon.rb	(working copy)
+@@ -205,6 +205,7 @@
+ 				xml =  amazon_call_ecs( asin, id_type )
+ 				File::open( "#{cache}/#{asin}.xml", 'wb' ) {|f| f.write( xml )}
+ 			end
++			xml.force_encoding('UTF-8')
+ 			doc = REXML::Document::new( xml ).root
+ 			item = doc.elements.to_a( '*/Item' )[0]
+ 			if pos == 'detail' then
+Index: tdiary.rb
+===================================================================
+--- tdiary.rb	(revision 97)
++++ tdiary.rb	(working copy)
+@@ -11,6 +11,7 @@
+ 
+ $:.insert( 1, File::dirname( __FILE__ ) + '/misc/lib' )
+ 
++require 'compatible'
+ require 'cgi'
+ require 'uri'
+ begin
+@@ -466,6 +467,7 @@
+ 			load
+ 
+ 			instance_variables.each do |v|
++				v = v.to_s
+ 				v.sub!( /@/, '' )
+ 				instance_eval( <<-SRC
+ 					def #{v}
+@@ -631,11 +633,10 @@
+ 				cgi_conf.untaint unless @secure
+ 				def_vars = ""
+ 				variables.each do |var| def_vars << "#{var} = nil\n" end
+-				eval( def_vars )
++				extend_vars = 'variables.each do |var| eval "@#{var} = #{var} if #{var} != nil" end'
+ 				Safe::safe( @secure ? 4 : 1 ) do
+-					eval( cgi_conf, binding, "(TDiary::Config#cgi_conf)", 1 )
++					eval( def_vars + cgi_conf + extend_vars, binding, "(TDiary::Config#cgi_conf)", 1 )
+ 				end
+-				variables.each do |var| eval "@#{var} = #{var} if #{var} != nil" end
+ 			rescue IOError, Errno::ENOENT
+ 			end
+ 		end
+Index: plugin/ja/05referer.rb
+===================================================================
+--- plugin/ja/05referer.rb	(revision 97)
++++ plugin/ja/05referer.rb	(working copy)
+@@ -31,7 +31,7 @@
+ 	<p>→<a href="#{h @update}?referer=volatile" target="referer">既存設定はこちら</a></p>
+ 	<p><textarea name="only_volatile" cols="70" rows="10">#{h @conf.only_volatile2.join( "\n" )}</textarea></p>
+ 	<h3 class="subtitle">#{label_referer_table}</h3>
+-	#{"<p>リンク元リストのURLを、特定の文字列に変換する対応表を指定できます。1件につき、URLと表示文字列を空白で区切って指定します。正規表現が使えるので、URL中に現れた「(～)」は、置換文字列中で「\\1」のような「\数字」で利用できます。</p>" unless @conf.mobile_agent?}
++	#{"<p>リンク元リストのURLを、特定の文字列に変換する対応表を指定できます。1件につき、URLと表示文字列を空白で区切って指定します。正規表現が使えるので、URL中に現れた「(～)」は、置換文字列中で「\\\\1」のような「\\数字」で利用できます。</p>" unless @conf.mobile_agent?}
+ 	<p>→<a href="#{h @update}?referer=table" target="referer">既存設定はこちら</a></p>
+ 	<p><textarea name="referer_table" cols="70" rows="10">#{h @conf.referer_table2.collect{|a|a.join( " " )}.join( "\n" )}</textarea></p>
+ 	HTML
+Index: index.rb
+===================================================================
+--- index.rb	(revision 97)
++++ index.rb	(working copy)
+@@ -5,7 +5,7 @@
+ # Copyright (C) 2001-2006, TADA Tadashi <sho@spc.gr.jp>
+ # You can redistribute it and/or modify it under GPL2.
+ #
+-BEGIN { $defout.binmode }
++BEGIN { $stdout.binmode }
+ $KCODE = 'n'
+ 
+ begin
Index: /tdiary/branches/upstream/contrib/util/rast-search/en/rast-register.rb
===================================================================
--- /tdiary/branches/upstream/contrib/util/rast-search/en/rast-register.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/util/rast-search/en/rast-register.rb (revision 710)
@@ -0,0 +1,3 @@
+@rast_register_conf_label = 'Rast Search'
+@rast_register_conf_header = 'Rebuild Rast search index'
+@rast_register_conf_description = 'To rebuild Rast search index, check the box and submit \'OK\'.'
Index: /tdiary/branches/upstream/contrib/util/rast-search/i.rast.rhtml
===================================================================
--- /tdiary/branches/upstream/contrib/util/rast-search/i.rast.rhtml (revision 710)
+++ /tdiary/branches/upstream/contrib/util/rast-search/i.rast.rhtml (revision 710)
@@ -0,0 +1,33 @@
+<%# i.rast.rhtml $Revision: 1.5.2.1 $ %>
+<h1><%= CGI::escapeHTML( @conf.html_title ) %> [全文検索]</h1>
+<form action="<%= _(@cgi.script_name) %>">
+<p>
+<input type="text" name="query" value="<%= _(@conf.to_native(@query)) %>">
+<input type="submit" value="検索">
+<a href="http://projects.netlab.jp/rast/query.html.ja">検索方法</a>
+</p>
+<p>
+並べ替え:
+<select name="sort">
+<%= @sort_options %>
+</select>
+<select name="order">
+<%= @order_options %>
+</select>
+表示件数:
+<select name="num">
+<%= @num_options %>
+</select>
+</p>
+</form>
+<% if @msg %>
+<p><%= @msg %></p>
+<% else %>
+<p><%= @result.hit_count %>件中<%= @start + 1 %>-<%= @start + @result.items.length %>件目</p>
+<% for item in @result.items %><% format_result_item(item) %>
+<h2><a href="<%= @conf.index %><%= @plugin.anchor(@date) %>"><%= _(@conf.to_native(@title)) %></a>(スコア:<%= item.score %>)</h2>
+<% end %>
+<% if @result.hit_count > @num %>
+<%= format_links(@result) %>
+<% end %>
+<% end %>
Index: /tdiary/branches/upstream/contrib/util/rast-search/README.ja
===================================================================
--- /tdiary/branches/upstream/contrib/util/rast-search/README.ja (revision 710)
+++ /tdiary/branches/upstream/contrib/util/rast-search/README.ja (revision 710)
@@ -0,0 +1,99 @@
+rast-search README
+===================
+
+  Rast <http://projects.netlab.jp/rast/> を用いた tDiary 検索環境です。
+
+
+特徴
+----------
+
+  日記の更新と連動して自動的にインデックスを更新するので、いつでも最新の
+  情報で検索することができます。日記を HTML 化した後に必要な部分だけを取
+  り出してインデックスを作成するので、ヘッダやフッタなどによる検索ノイズ
+  がなく、また、プラグインの出力が検索対象になるという特徴があります。
+
+
+必要なもの
+----------
+
+  * tDiary 1.5 以降
+  * Ruby 1.8.2 以降
+  * Rast 0.3.1 以降
+
+
+セットアップ
+------------
+
+  1. rast-register.rb を tDiary の プラグインディレクトリにコピーします。
+
+  2. rast-search.rb を tDiary の index.rb があるディレクトリにコピーしま
+     す。必要なら index.rb と同じようにシンボリックリンクを張ったり名前を
+     変えたりしてください。
+
+  3. CGI として実行可能にします。
+
+     $ chmod a+x rast-search.rb
+
+  4. 必要なら #! のパスを変更します。
+
+  5. rast.rhtml と rast.rxml と i.rast.rhtml を tDiary の skel/ ディレク
+     トリにコピーします。
+
+  6. rast-register.rb プラグインを有効にします。(tDiary の plugin/ ディレ
+     クトリにコピーするか、プラグイン選択のディレクトリにコピーしてブラウ
+     ザから有効に設定します。言語リソースファイルの en/rast-register.rb
+     と ja/rast-register.rb も、プラグインディレクトリの en/ 以下および
+     ja/ 以下にコピーしてください。)
+
+  7. (オプション) tdiary.conf で以下のように encoding の設定をできます。
+     Rast の encoding module の名前で設定してください。デフォルトは
+     'euc_jp' です。encoding の設定を変更する場合は、Rast のインデックス
+     があるディレクトリ (cache ディレクトリの下の /rast) を消して、再度イ
+     ンデックスを作りなおしてください。
+
+     @options['rast.index'] = 'utf8'
+
+  8. 既存の日記コンテンツに対して検索インデックスを作成します。tDiary の
+     設定画面から「Rast検索」を選び、「Rast検索のインデックスを再構築する
+     場合は、チェックボックスをチェックしてOKを押してください」というメッ
+     セージに従ってチェックしてOKを押すと、
+
+     インデックスの作成は、tDiary の CGI の実行権限で以下のように実行する
+     ことでも可能です。
+
+     ruby rast-register.rb [-p tdiary.rbのあるディレクトリ] [-c tdiary.confのあるディレクトリ]
+
+  9. 自分の tDiary の好きな場所 (例えばヘッダ) に以下のようなフォームを加
+     えてください。
+
+     <form method="get" action="rast-search.rb" class="search">
+     <input type="text" name="query" size="20" value="">
+     <input type="submit" value="Search">
+     </form>
+
+     search-form.rb プラグインを有効にしている場合は、以下のように書くこ
+     ともできます。
+
+     <%= search_form('rast-search.rb', 'query') %>
+
+  以上です。
+
+
+検索のしかた
+------------
+
+  rast-search の検索対象は、日記本文、ツッコミ、TrackBack です。
+
+  検索方法については、 http://projects.netlab.jp/rast/query.html.ja をご覧くだ
+  さい。
+
+
+連絡先
+------
+
+  かずひこ <kazuhiko@fdiary.net>
+  http://www.fdiary.net/
+
+  バグ報告は以下のどこかにお願いします。
+    * tdiary-devel ML
+    * 直接メール
Index: /tdiary/branches/upstream/contrib/util/rast-search/ChangeLog
===================================================================
--- /tdiary/branches/upstream/contrib/util/rast-search/ChangeLog (revision 710)
+++ /tdiary/branches/upstream/contrib/util/rast-search/ChangeLog (revision 710)
@@ -0,0 +1,55 @@
+2007-01-08  Kazuhiko  <kazuhiko@fdiary.net>
+	* rast-register.rb, rast-search.rb: revise for UTF8.
+
+2005-12-10  Kazuhiko  <kazuhiko@fdiary.net>
+	* rast-register.rb: set 'diaries' for the 'my-ex' plugin.
+
+	* rast-register.rb: revise attributes of the 'date' property.
+	rebuilding indices is necessary.
+	* rast-search.rb: revise for mod_ruby.
+
+2005-12-06  Kazuhiko  <kazuhiko@fdiary.net>
+	* rast.rhtml: remove a dummy link.
+
+2005-12-05  Kazuhiko  <kazuhiko@fdiary.net>
+	* rast-register.rb: revise attributes of the 'date' property.
+	remove indices before rebuilding.
+
+2005-12-04  Kazuhiko  <kazuhiko@fdiary.net>
+	* rast-register.rb: support tDiary-2.0.x.
+	* en/rast-register.rb, ja/rast-register.rb: add resources.
+
+2005-11-24  Kazuhiko  <kazuhiko@fdiary.net>
+	* rast-register.rb: fix a bug in command-line mode (reported by
+	YAA). set 'cache_path' for the 'tlink' plugin (reported by YAA).
+
+	* rast-register.rb: support rebuilding index by conf mode. abolish
+	'CGI' mode.
+
+2005-11-23  Kazuhiko  <kazuhiko@fdiary.net>
+	* rast-register.rb: use 'require' instead of 'autoload'.
+
+2005-11-21  Kazuhiko  <kazuhiko@fdiary.net>
+	* rast-register.rb: add a plugin file.
+	* rast-search.rb, rast.rxml: support opensearch rss.
+	* rast.rhtml: revise for more valid HTML output.
+
+2005-05-11  Kazuhiko  <kazuhiko@fdiary.net>
+	* rast.rhtml, i.rast.rhtml: use @num instead of 10.
+
+2005-05-11 TADA Tadashi <sho@spc.gr.jp>
+	* rast.rhtml, i.rast.rhtml: set title to h2 element, and copy format_links
+	plugin on top of results.
+
+2005-05-10 TADA Tadashi <sho@spc.gr.jp>
+	* README.ja: fix typo.
+
+2005-04-16  Kazuhiko  <kazuhiko@fdiary.net>
+	* rast.rhtml: convert the encoding of @query.
+
+2005-04-15  Kazuhiko  <kazuhiko@fdiary.net>
+	* rast-search.rb: never use Rast::LocalDB. fix a bug in running
+	from a shell.
+
+2005-04-14  Kazuhiko  <kazuhiko@fdiary.net>
+	* initial release of rast-search.
Index: /tdiary/branches/upstream/contrib/util/rast-search/ja/rast-register.rb
===================================================================
--- /tdiary/branches/upstream/contrib/util/rast-search/ja/rast-register.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/util/rast-search/ja/rast-register.rb (revision 710)
@@ -0,0 +1,3 @@
+@rast_register_conf_label = 'Rast検索'
+@rast_register_conf_header = 'Rast検索インデックスの再構築'
+@rast_register_conf_description = 'Rast検索のインデックスを再構築する場合は、チェックボックスをチェックしてOKを押してください。'
Index: /tdiary/branches/upstream/contrib/util/rast-search/rast.rxml
===================================================================
--- /tdiary/branches/upstream/contrib/util/rast-search/rast.rxml (revision 710)
+++ /tdiary/branches/upstream/contrib/util/rast-search/rast.rxml (revision 710)
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="<%= @conf.encoding %>"?>
+<rss xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/" version="2.0">
+  <channel>
+    <title><%= _( @conf.html_title ) %> [全文検索]: <%= _(@query) %></title>
+    <link><%= @conf.base_url.sub(%r|/[^/]*$|, '/') %><%= _(@cgi.script_name ? File.basename(@cgi.script_name) : "") %><%= format_anchor(@start, @num) %></link>
+    <description>Rast 検索: <%= _(@query) %></description>
+    <language>ja-JP</language>
+    <openSearch:totalResults><%= @result.hit_count %></openSearch:totalResults>
+    <openSearch:startIndex><%= @start + 1 %></openSearch:startIndex>
+    <openSearch:itemsPerPage><%= @num %></openSearch:itemsPerPage>
+    <% for item in @result.items %>
+    <% format_result_item(item) %>
+    <item>
+      <title><%= _(@conf.to_native(@title)) %></title>
+      <link><%= @conf.base_url %><%= @plugin.anchor(@date) %></link>
+      <pubDate><%= _(CGI.rfc1123_date(Time.parse(@last_modified))) %></pubDate>
+      <description><%= @conf.to_native(@summary) %></description>
+    </item>
+    <% end %>
+  </channel>
+</rss>
Index: /tdiary/branches/upstream/contrib/util/rast-search/rast-register.rb
===================================================================
--- /tdiary/branches/upstream/contrib/util/rast-search/rast-register.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/util/rast-search/rast-register.rb (revision 710)
@@ -0,0 +1,326 @@
+#!/usr/bin/env ruby
+#
+# Copyright (C) 2005 Kazuhiko <kazuhiko@fdiary.net>
+# You can redistribute it and/or modify it under GPL2.
+#
+
+mode = ""
+if $0 == __FILE__
+	require 'cgi'
+	ARGV << '' # dummy argument against cgi.rb offline mode.
+	@cgi = CGI::new
+	mode = "CMD"
+else
+	mode = "PLUGIN"
+end
+
+if mode == "CMD"
+	tdiary_path = "."
+	tdiary_conf = "."
+	$stdout.sync = true
+
+	def usage
+		puts "rast-register.rb $Revision: 1.10.2.2 $"
+		puts " register to rast index files from tDiary's database."
+		puts " usage: ruby rast-regiser.rb [-p <tDiary directory>] [-c <tdiary.conf directory>]"
+		exit
+	end
+
+	require 'getoptlong'
+	parser = GetoptLong::new
+	parser.set_options(['--path', '-p', GetoptLong::REQUIRED_ARGUMENT], ['--conf', '-c', GetoptLong::REQUIRED_ARGUMENT])
+	begin
+		parser.each do |opt, arg|
+			case opt
+			when '--path'
+				tdiary_path = arg
+			when '--conf'
+				tdiary_conf = arg
+			end
+		end
+	rescue
+		usage
+		exit( 1 )
+	end
+
+	tdiary_conf = tdiary_path unless tdiary_conf
+	Dir::chdir( tdiary_conf )
+
+	begin
+		$:.unshift tdiary_path
+		require "#{tdiary_path}/tdiary"
+	rescue LoadError
+		$stderr.puts "rast-register.rb: cannot load tdiary.rb. <#{tdiary_path}/tdiary>\n"
+		$stderr.puts " usage: ruby rast-regiser.rb [-p <tDiary directory>] [-c <tdiary.conf directory>]"
+		exit( 1 )
+	end
+end
+
+require 'rast'
+
+module TDiary
+	#
+	# Database
+	#
+	class RastDB
+		DB_OPTIONS = {
+			"preserve_text" => true,
+			"properties" => [
+				{
+					"name" => "title",
+					"type" => Rast::PROPERTY_TYPE_STRING,
+					"search" => false,
+					"text_search" => true,
+					"full_text_search" => true,
+					"unique" => false,
+				},
+				{
+					"name" => "user",
+					"type" => Rast::PROPERTY_TYPE_STRING,
+					"search" => true,
+					"text_search" => false,
+					"full_text_search" => false,
+					"unique" => false,
+				},
+				{
+					"name" => "date",
+					"type" => Rast::PROPERTY_TYPE_STRING,
+					"search" => true,
+					"text_search" => true,
+					"full_text_search" => false,
+					"unique" => false,
+				},
+				{
+					"name" => "last_modified",
+					"type" => Rast::PROPERTY_TYPE_DATE,
+					"search" => true,
+					"text_search" => false,
+					"full_text_search" => false,
+					"unique" => false,
+				}
+			]
+		}
+
+		attr_accessor :db
+		attr_reader :conf, :encoding
+
+		def initialize(conf, encoding)
+			@conf = conf
+			@encoding = encoding
+			@db_options = {'encoding' => @encoding}.update(DB_OPTIONS)
+			@db_options['properties'].delete_if{|i| i['name'] == 'user'} unless @conf['rast.with_user_name']
+		end
+
+		def transaction
+			if !File.exist?(db_path)
+				Rast::DB.create(db_path, @db_options)
+			end
+			Rast::DB.open(db_path, Rast::DB::RDWR, "sync_threshold_chars" => 500000) { |@db|
+				yield self
+			}
+		end
+
+                def cache_path
+                        @conf.cache_path || "#{@conf.data_path}cache"
+                end
+
+		def db_path
+			@conf['rast.db_path'] || "#{cache_path}/rast".untaint
+		end
+	end
+
+	#
+	# Register
+	#
+	class RastRegister < TDiaryBase
+		def initialize(rast_db, diary)
+			@db = rast_db.db
+			super(CGI::new, 'day.rhtml', rast_db.conf)
+			@diary = diary
+			@encoding = rast_db.encoding
+			@date = diary.date
+			@diaries = {@date.strftime('%Y%m%d') => @diary} if @diaries.empty?
+			@plugin = ::TDiary::Plugin::new(
+							'conf' => @conf,
+							'cgi' => @cgi,
+							'cache_path' => cache_path,
+							'diaries' => @diaries
+							)
+			def @plugin.apply_plugin_alt( str, remove_tag = false )
+				apply_plugin( str, remove_tag )
+			end
+		end
+
+		def execute(force = false)
+			date = @date.strftime('%Y%m%d')
+			last_modified = @diary.last_modified.strftime("%FT%T")
+			options = {"properties" => ['last_modified']}
+			if @conf['rast.with_user_name']
+				result = @db.search("date : #{date} & user = #{@conf.user_name}", options)
+			else
+				result = @db.search("date : #{date}", options)
+			end
+			for item in result.items
+				if force || item.properties[0] < last_modified
+					@db.delete(item.doc_id)
+				else
+					return
+				end
+			end
+			return unless @diary.visible?
+
+			# body
+			index = 0
+			anchor = ''
+			@diary.each_section do |section|
+				index += 1
+				@conf['apply_plugin'] = true
+				anchor = "#{date}p%02d" % index
+				title = CGI.unescapeHTML( @plugin.apply_plugin_alt( section.subtitle_to_html, true ).strip )
+				if title.empty?
+					title = @plugin.apply_plugin_alt( section.body_to_html, true ).strip
+					title = @conf.shorten( CGI.unescapeHTML( title ), 20 )
+				end
+				body = CGI.unescapeHTML( @plugin.apply_plugin_alt( section.body_to_html, true ).strip )
+				properties = {
+					"title" => title,
+					"date" => anchor,
+					"last_modified" => last_modified,
+				}
+				properties["user"] = @conf.user_name if @conf['rast.with_user_name']
+				@db.register(body, properties)
+			end
+
+			# comment
+			@diary.each_visible_comment( 100 ) do |comment, index|
+				if /^(TrackBack|Pingback)$/i =~ comment.name
+					anchor = "#{date}t%02d" % index
+					title = "TrackBack (#{comment.name})"
+				else
+
+
+					anchor = "#{date}c%02d" % index
+					title = "#{@plugin.comment_description_short} (#{comment.name})"
+				end
+				body = comment.body
+				properties = {
+					"title" => title,
+					"date" => anchor,
+					"last_modified" => comment.date.strftime("%FT%T"),
+				}
+				properties["user"] = @conf.user_name if @conf['rast.with_user_name']
+				@db.register(body, properties)
+			end
+		end
+		
+		protected
+
+		def mode; 'day'; end
+		def cookie_name; ''; end
+		def cookie_mail; ''; end
+
+		def convert(str)
+			str
+		end
+	end
+
+	#
+	# Main
+	#
+	class RastRegisterMain < TDiaryBase
+		def initialize(conf)
+			super(CGI::new, 'day.rhtml', conf)
+		end
+
+		def execute(encoding, out = $stdout)
+			require 'fileutils'
+			calendar
+			db = RastDB.new(conf, encoding)
+			FileUtils.rm_rf(db.db_path)
+			db.transaction do |rast_db|
+				@years.keys.sort.each do |year|
+					out << "(#{year.to_s}/) "
+					@years[year.to_s].sort.each do |month|
+						@io.transaction(Time::local(year.to_i, month.to_i)) do |diaries|
+							diaries.sort.each do |day, diary|
+								RastRegister.new(rast_db, diary).execute
+								out << diary.date.strftime('%m%d ')
+							end
+							false
+						end
+					end
+				end
+			end
+		end
+	end
+end
+
+if mode == "CMD"
+	begin
+		require 'cgi'
+		if TDiary::Config.instance_method(:initialize).arity > 0
+			# for tDiary 2.1 or later
+			cgi = CGI.new
+			conf = TDiary::Config::new(cgi)
+		else
+			# for tDiary 2.0 or earlier
+			conf = TDiary::Config::new
+		end
+		conf.header = ''
+		conf.footer = ''
+		conf.show_comment = true
+		conf.hide_comment_form = true
+		conf.show_nyear = false
+		def conf.bot?; true; end
+		encoding = 'utf8'
+		TDiary::RastRegisterMain.new(conf).execute(encoding)
+	rescue
+		print $!, "\n"
+		$@.each do |v|
+			print v, "\n"
+		end
+		exit( 1 )
+	end
+
+	puts
+else
+	add_update_proc do
+		conf = @conf.clone
+		conf.header = ''
+		conf.footer = ''
+		conf.show_comment = true
+		conf.hide_comment_form = true
+		conf.show_nyear = false
+		def conf.bot?; true; end
+	
+		diary = @diaries[@date.strftime('%Y%m%d')]
+		encoding = 'utf8'
+		TDiary::RastDB.new(conf, encoding).transaction do |rast_db|
+			TDiary::RastRegister.new(rast_db, diary).execute(true)
+		end
+	end
+
+	if !@conf['rast_register.hideconf'] && (@mode == 'conf' || @mode == 'saveconf')
+		args = ['rast_register', @rast_register_conf_label]
+		args << 'update' if TDIARY_VERSION > '2.1.3'
+		add_conf_proc(*args) do
+			str = <<-HTML
+<h3 class="subtitle">#{@rast_register_conf_header}</h3>
+<p>
+<label for="rast_register_rebuild"><input id="rast_register_rebuild" type="checkbox" name="rast_register_rebuild" value="1">
+#{@rast_register_conf_description}</label>
+</p>
+HTML
+			if @mode == 'saveconf'
+				if @cgi.valid?( 'rast_register_rebuild' )
+					encoding = 'utf8'
+					str << '<p>The following diaries were registered.</p>'
+					out = ''
+					TDiary::RastRegisterMain.new(@conf).execute(encoding, out)
+					str << "<p>#{out}</p>"
+				end
+			end
+			str
+		end
+	end
+end
Index: /tdiary/branches/upstream/contrib/util/rast-search/rast-search.rb
===================================================================
--- /tdiary/branches/upstream/contrib/util/rast-search/rast-search.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/util/rast-search/rast-search.rb (revision 710)
@@ -0,0 +1,248 @@
+#!/usr/bin/env ruby
+# rast-search.rb $Revision: 1.6.2.2 $
+#
+# Copyright (C) 2005 Kazuhiko <kazuhiko@fdiary.net>
+# You can redistribute it and/or modify it under GPL2.
+#
+$KCODE= 'u'
+BEGIN { $defout.binmode }
+
+if FileTest::symlink?( __FILE__ ) then
+	org_path = File::dirname( File::readlink( __FILE__ ) )
+else
+	org_path = File::dirname( __FILE__ )
+end
+$:.unshift( org_path.untaint )
+require 'tdiary'
+require 'rast'
+
+#
+# class TDiaryRast
+#
+module TDiary
+	class TDiaryRast < ::TDiary::TDiaryBase
+		MAX_PAGES = 20
+		SORT_OPTIONS = [
+			["score", "スコア順"],
+			["date", "日付順"],
+		]
+		SORT_PROPERTIES = ["date"]
+		ORDER_OPTIONS = [
+			["asc", "昇順"],
+			["desc", "降順"],
+		]
+		NUM_OPTIONS = [10, 20, 30, 50, 100]
+
+		def initialize( cgi, rhtml, conf )
+			super
+			@db_path = conf.options['rast.db_path'] || "#{cache_path}/rast"
+			@encoding = 'utf8'
+			# conf.options['sp.selected'] = ''
+			parse_args
+			format_form
+			if @query.empty?
+				@msg = '検索条件を入力して、「検索」ボタンを押してください'
+			else
+				search
+			end
+		end
+
+		def load_plugins
+			super
+			# add a opensearch rss link
+			@plugin.instance_variable_get('@header_procs').unshift Proc.new {
+				cgi_url = @conf.base_url.sub(%r|/[^/]*$|, '/') + (@cgi.script_name ? _(File.basename(@cgi.script_name)) : '')
+				%Q|\t<link rel="alternate" type="application/rss+xml" title="Search Result RSS" href="#{cgi_url}#{format_anchor(@start, @num)};type=rss">\n|
+			}
+			# override some plugins
+			def @plugin.sn(number = nil); ''; end
+		end
+
+		def eval_rxml
+			require 'time'
+			load_plugins
+			ERB::new( File::open( "#{PATH}/skel/rast.rxml" ){|f| f.read }.untaint ).result( binding )
+		end
+
+		private
+
+		def parse_args
+			@query = @cgi["query"].strip
+			@start = @cgi["start"].to_i
+			@num = @cgi["num"].to_i
+			if @num < 1
+				@num = 10
+			elsif @num > 100
+				@num = 100
+			end
+			@sort = @cgi["sort"].empty? ? "score" : @cgi["sort"]
+			@order = @cgi["order"].empty? ? "desc" : @cgi["order"]
+		end
+
+		def search
+			db = Rast::DB.open(@db_path, Rast::DB::RDONLY)
+			begin
+				options = create_search_options
+				t = Time.now
+				@result = db.search(convert(@query), options)
+				@secs = Time.now - t
+			rescue
+				@msg = "エラー: #{_($!.to_s)}</p>"
+			ensure
+				db.close
+			end
+		end
+
+		def format_result_item(item)
+			if @conf['rast.with_user_name']
+				@title, @user, @date, @last_modified = *item.properties
+			else
+				@title, @date, @last_modified = *item.properties
+			end
+			@summary = _(item.summary) || ''
+			for term in @result.terms
+				@summary.gsub!(Regexp.new(Regexp.quote(CGI.escapeHTML(term.term)), true, @encoding), "<strong>\\&</strong>")
+			end
+		end
+
+		def format_links(result)
+			page_count = (result.hit_count - 1) / @num + 1
+			current_page = @start / @num + 1
+			first_page = current_page - (MAX_PAGES / 2 - 1)
+			if first_page < 1
+				first_page = 1
+			end
+			last_page = first_page + MAX_PAGES - 1
+			if last_page > page_count
+				last_page = page_count
+			end
+			buf = "<p class=\"infobar\">\n"
+			if current_page > 1
+				buf.concat(format_link("前へ", @start - @num, @num))
+			end
+			if first_page > 1
+				buf.concat("... ")
+			end
+			for i in first_page..last_page
+				if i == current_page
+					buf.concat("#{i} ")
+				else
+					buf.concat(format_link(i.to_s, (i - 1) * @num, @num))
+				end
+			end
+			if last_page < page_count
+				buf.concat("... ")
+			end
+			if current_page < page_count
+				buf.concat(format_link("次へ", @start + @num, @num))
+			end
+			buf.concat("</p>\n")
+			return buf
+		end
+
+		def format_anchor(start, num)
+			return format('?query=%s;start=%d;num=%d;sort=%s;order=%s', CGI::escape(@query), start, num, _(@sort), _(@order))
+		end
+
+		def format_link(label, start, num)
+			return format('<a href="%s%s">%s</a> ',	_(@cgi.script_name ? @cgi.script_name : ""),  format_anchor(start, num), _(label))
+		end
+
+		def create_search_options
+			options = {
+				"properties" => [
+					"title", "date", 'last_modified'
+				],
+				"need_summary" => true,
+				"summary_nchars" => 150,
+				"start_no" => @start,
+				"num_items" => @num
+			}
+			options['properties'] << 'user' if @conf['rast.with_user_name']
+			if SORT_PROPERTIES.include?(@sort)
+				options["sort_method"] = Rast::SORT_METHOD_PROPERTY
+				options["sort_property"] = @sort
+			end
+			if @order == "asc"
+				options["sort_order"] = Rast::SORT_ORDER_ASCENDING
+			else
+				options["sort_order"] = Rast::SORT_ORDER_DESCENDING
+			end
+			return options
+		end
+
+		def format_options(options, value)
+			return options.collect { |val, label|
+				if val == value
+					"<option value=\"#{_(val)}\" selected>#{_(label)}</option>"
+				else
+					"<option value=\"#{_(val)}\">#{_(label)}</option>"
+				end
+			}.join("\n")
+		end
+
+		def format_form
+			@num_options = NUM_OPTIONS.collect { |n|
+				if n == @num
+					"<option value=\"#{n}\" selected>#{n}件ずつ</option>"
+				else
+					"<option value=\"#{n}\">#{n}件ずつ</option>"
+				end
+			}.join("\n")
+			@sort_options = format_options(SORT_OPTIONS, @sort)
+			@order_options = format_options(ORDER_OPTIONS, @order)
+		end
+
+		def _(str)
+			CGI::escapeHTML(str)
+		end
+
+		def convert(str)
+			@conf.to_native(str)
+		end
+	end
+end
+
+begin
+	@cgi = CGI::new
+	if ::TDiary::Config.instance_method(:initialize).arity > 0
+		# for tDiary 2.1 or later
+		conf = ::TDiary::Config::new(@cgi)
+	else
+		# for tDiary 2.0 or earlier
+		conf = ::TDiary::Config::new
+	end
+	tdiary = TDiary::TDiaryRast::new( @cgi, 'rast.rhtml', conf )
+
+	head = {
+		'type' => 'text/html',
+		'Vary' => 'User-Agent'
+	}
+	if @cgi.mobile_agent? then
+		body = conf.to_mobile( tdiary.eval_rhtml( 'i.' ) )
+		head['charset'] = conf.mobile_encoding
+		head['Content-Length'] = body.size.to_s
+	else
+		if @cgi['type'] == 'rss'
+			head['type'] = "application/xml; charset=#{conf.encoding}"
+			body = tdiary.eval_rxml
+		else
+			body = tdiary.eval_rhtml
+		end
+		head['charset'] = conf.encoding
+		head['Content-Length'] = body.size.to_s
+		head['Pragma'] = 'no-cache'
+		head['Cache-Control'] = 'no-cache'
+	end
+	print @cgi.header( head )
+	print body
+rescue Exception
+	if @cgi then
+		print @cgi.header( 'type' => 'text/plain' )
+	else
+		print "Content-Type: text/plain\n\n"
+	end
+	puts "#$! (#{$!.class})"
+	puts ""
+	puts $@.join( "\n" )
+end
Index: /tdiary/branches/upstream/contrib/util/rast-search/rast.rhtml
===================================================================
--- /tdiary/branches/upstream/contrib/util/rast-search/rast.rhtml (revision 710)
+++ /tdiary/branches/upstream/contrib/util/rast-search/rast.rhtml (revision 710)
@@ -0,0 +1,66 @@
+<%# rast.rhtml $Revision: 1.7.2.1 $ %>
+<div class="adminmenu"><%%=navi_user%></div>
+
+<h1><%= CGI::escapeHTML( @conf.html_title ) %> [全文検索]</h1>
+
+<form action="<%= @cgi.script_name ? _(@cgi.script_name) : '' %>">
+<p>
+<input type="text" name="query" value="<%= _(@conf.to_native(@query)) %>" size="60">
+<input type="submit" value="検索">
+<a href="http://projects.netlab.jp/rast/query.html.ja">検索方法</a>
+</p>
+<p>
+並べ替え:
+<select name="sort">
+<%= @sort_options %>
+</select>
+<select name="order">
+<%= @order_options %>
+</select>
+
+表示件数:
+<select name="num">
+<%= @num_options %>
+</select>
+</p>
+</form>
+
+<% if @msg %>
+  <p><%= @msg %></p>
+<% else %>
+  <p>
+    <%= _(@conf.to_native(@query)) %> の検索結果
+    <%= @result.hit_count %> 件中 <%= @start + 1 %> - <%= @start + @result.items.length %> 件目
+    (<%= @secs %> 秒)
+  </p>
+  <% if @result.hit_count > @num %>
+    <%= format_links(@result) %>
+  <% end %>
+  <% for item in @result.items %>
+    <% format_result_item(item) %>
+<div class="day">
+<h2><span class="date"></span> <span class="title"><a href="<%= @conf.index %><%= @plugin.anchor(@date) %>"><%= _(@conf.to_native(@title)) %></a></span></h2>
+<div class="body">
+<div class="section">
+<p>
+<%= @conf.to_native(@summary) %>
+</p>
+</div>
+</div>
+<div class="comment">
+<div class="commentshort">
+<p><%= @conf.comment_anchor %>
+<span class="commentator"></span>&nbsp;(スコア:<%= item.score %>)</p>
+</div>
+</div>
+<div class="referer">
+</div>
+</div>
+  <% end %>
+  <% if @result.hit_count > @num %>
+    <%= format_links(@result) %>
+  <% end %>
+<% end %>
+
+<div class="footer">
+Powered by <a href="http://projects.netlab.jp/rast/">Rast</a> <%= ::Rast::VERSION %></div>
Index: /tdiary/branches/upstream/contrib/util/tdiary-mode/tdiary-mode.el
===================================================================
--- /tdiary/branches/upstream/contrib/util/tdiary-mode/tdiary-mode.el (revision 710)
+++ /tdiary/branches/upstream/contrib/util/tdiary-mode/tdiary-mode.el (revision 710)
@@ -0,0 +1,613 @@
+;;; tdiary-mode.el -- Major mode for tDiary editing -*- coding: euc-jp -*-
+
+;; Copyright (C) 2002 Junichiro Kita
+
+;; Author: Junichiro Kita <kita@kitaj.no-ip.com>
+
+;; $Id: tdiary-mode.el,v 1.4 2005/08/19 08:36:43 tadatadashi Exp $
+;;
+;; This program is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation; either version 2, or (at
+;; your option) any later version.
+
+;; This program is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
+;; General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; usage:
+;;
+;; Put the following in your .emacs file:
+;;
+;;  (setq tdiary-diary-list '(("my 1st diary" "http://example.com/tdiary/")
+;;                            ("my 2nd diary" "http://example.com/tdiary2/")))
+;;  (setq tdiary-text-directory (expand-file-name "~/path-to-saved-diary"))
+;;  (setq tdiary-browser-function 'browse-url)
+;;  (autoload 'tdiary-mode "tdiary-mode" nil t)
+;;  (autoload 'tdiary-new "tdiary-mode" nil t)
+;;  (autoload 'tdiary-new-diary "tdiary-mode" nil t)
+;;  (autoload 'tdiary-replace "tdiary-mode" nil t)
+;;  (add-to-list 'auto-mode-alist
+;;		'("\\.td$" . tdiary-mode))
+;;
+;; You can use your own plugin completion, keybindings:
+;;
+;;  (setq tdiary-plugin-definition
+;;	  '(
+;;	    ("STRONG" ("<%=STRONG %Q|" (p "str: ") "| %>"))
+;;	    ("PRE" ("<%=PRE %Q|" (p "str: ") "| %>"))
+;;	    ("CITE" ("<%=CITE %Q|" (p "str: ") "| %>"))
+;;	    ))
+;;
+;;  (add-hook 'tdiary-mode-hook
+;;	      '(lambda ()
+;;		 (local-set-key "\C-i" 'tdiary-complete-plugin)))
+;;
+;; If you want to save username and password cache to file:
+;;
+;;  (setq tdiary-passwd-file (expand-file-name "~/.tdiary-pass"))
+;;
+;; then, M-x tdiary-passwd-cache-save.	!!DANGEROUS!!
+;;
+;; see http://kitaj.no-ip.com/rw-cgi.rb?cmd=view;name=tdiary-mode
+;;
+
+;; ToDo:
+;; - find plugin definition automatically(needs modification for plugin)
+;; - bug fix
+;;
+
+;;; Variable:
+
+(require 'http)
+(require 'poe)
+(require 'tempo)
+
+(defvar tdiary-diary-list nil
+  "List of diary list.
+Each element looks like (NAME URL) or (NAME URL INDEX-RB UPDATE-RB).")
+
+(defvar tdiary-diary-name nil
+  "Identifier for diary to be updated.")
+
+(defvar tdiary-diary-url nil
+  "tDiary-mode updates this URL. URL should end with '/'.")
+
+(defvar tdiary-index-rb nil
+  "Name of the 'index.rb'.")
+
+(defvar tdiary-update-rb "update.rb"
+  "Name of the 'update.rb'.")
+
+(defvar tdiary-csrf-key nil
+  "CSRF protection key.")
+
+(defvar tdiary-coding-system 'euc-japan-dos)
+
+(defvar tdiary-title nil
+  "Title of diary")
+
+(defvar tdiary-style-mode 'html-mode
+  "Major mode to be used for tDiary editing.")
+
+(defvar tdiary-date nil
+  "Date to be updated.")
+
+(defvar tdiary-edit-mode nil)
+
+(defvar tdiary-plugin-list nil
+  "A List of pairs of a plugin name and its completing command.
+It is used in `tdiary-complete-plugin'.")
+
+(defvar tdiary-tempo-tags nil
+  "Tempo tags for tDiary mode")
+
+(defvar tdiary-completion-finder "\\(\\(<\\|&\\|<%=\\).*\\)\\="
+  "Regexp used to find tags to complete.")
+
+(defvar tdiary-plugin-initial-definition
+  '(
+    ("my" ("<%=my %Q|" (p "a: ") "|, %Q|" (p "str: ") "| %>"))
+    ("fn" ("<%=fn %Q|" (p "footnote: ") "| %>"))
+    )
+  "Initial definition for tDiary tempo."
+)
+
+(defvar tdiary-edit-mode-list '(("append") ("replace")))
+
+(defvar tdiary-plugin-definition nil
+  "A List of definitions for tDiary tempo.
+Each element looks like (NAME ELEMENTS).  NAME is a string that
+contains the name of plugin, and ELEMENTS is a list of elements in the
+template.  See tempo.info for details.")
+
+(defvar tdiary-complete-plugin-history nil
+  "Minibuffer history list for `tdiary-complete-plugin'.")
+
+(defvar tdiary-passwd-file nil)
+(defvar tdiary-passwd-file-mode 384) ;; == 0600
+(defvar tdiary-text-directory-mode 448) ;; == 0700
+
+(defvar tdiary-passwd-cache nil
+  "Cache for username and password.")
+
+(defvar tdiary-hour-offset 0
+  "Offset to current-time.
+`tdiary-today' returns (current-time + tdiary-hour-offset).")
+
+(defvar tdiary-text-suffix ".td")
+
+(defvar tdiary-text-directory nil
+  "Directory where diary is stored.")
+
+(defvar tdiary-text-save-p nil
+  "Flag for saving text.
+If non-nil, tdiary buffer is associated to a real file, 
+named `tdiary-date' + `tdiary-text-suffix'.")
+
+(defvar tdiary-browser-function nil
+  "Function to call browser.
+If non-nil, `tdiary-update' calls this function.  The function
+is expected to accept only one argument(URL).")
+
+(defvar tdiary-mode-hook nil
+  "Hook run when entering tDiary mode.")
+
+(defvar tdiary-init-file "~/.tdiary"
+  "Init file for tDiary-mode.")
+
+;(defvar tdiary-plugin-dir nil
+;  "Path to plugins.  It must be a mounted file system.")
+;(defvar tdiary-plugin-definition-regexp "^[ \t]*def[ \t]+\\(.+?\\)[ \t]*\\(?:$\\|;\\|([ \t]*\\(.*?\\)[ \t]*)\\)")
+
+
+;;; Code:
+(defun tdiary-tempo-add-tag (def)
+  (let* ((plugin (car def))
+	 (element (cadr def))
+	 (name (concat "tdiary-" plugin))
+	 (completer (concat "<%=" plugin))
+	 (doc (concat "Insert `" plugin "'"))
+	 (command (tempo-define-template name element completer doc
+					 'tdiary-tempo-tags)))
+    (add-to-list 'tdiary-plugin-list (list plugin command))))
+
+(defun tdiary-tempo-define (l)
+  (mapcar 'tdiary-tempo-add-tag l))
+
+
+;(defun tdiary-parse-plugin-args (args)
+;  (if (null args)
+;      nil
+;    (mapcar '(lambda (x)
+;	       (let ((l (split-string x "[ \t]*=[ \t]*")))
+;		 (if (eq (length l) 1)
+;		     (car l)
+;		   l)))
+;	    (split-string args "[ \t]*,[ \t]*"))))
+
+;(defun tdiary-update-plugin-definition ()
+;  (interactive)
+;  (let ((files (directory-files tdiary-plugin-dir t ".*\\.rb$" nil t))
+;	(buf (generate-new-buffer "*update plugin*")))
+;    (save-excursion
+;      (save-window-excursion
+;	(switch-to-buffer buf)
+;	(mapc 'insert-file-contents files)
+;	(setq tdiary-plugin-definition nil)
+;	(while (re-search-forward tdiary-plugin-definition-regexp nil t)
+;	  (add-to-list 'tdiary-plugin-definition
+;		       (list (match-string 1)
+;			     (tdiary-parse-plugin-args (match-string 2)))))
+;	(kill-buffer buf)))))
+
+(defun tdiary-do-complete-plugin (&optional name)
+  "Complete function for plugin."
+  (let (command)
+    (when (null name)
+      (setq name 
+	    (completing-read "plugin: " tdiary-plugin-list nil nil nil
+			     'tdiary-complete-plugin-history)))
+    (setq command (cadr (assoc name tdiary-plugin-list)))
+    (when command
+      (funcall command))))
+
+;; derived from tempo.el
+(defun tdiary-complete-plugin (&optional silent)
+  "Look for a HTML tag or plugin and expand it.
+If there are no completable text, call `tdiary-do-complete-plugin'."
+  (interactive "*")
+  (let* ((collection (tempo-build-collection))
+	 (match-info (tempo-find-match-string tempo-match-finder))
+	 (match-string (car match-info))
+	 (match-start (cdr match-info))
+	 (exact (assoc match-string collection))
+	 (compl (or (car exact)
+		    (and match-info (try-completion match-string collection)))))
+    (if compl (delete-region match-start (point)))
+    (cond ((or (null match-info)
+	       (null compl))
+	   (tdiary-do-complete-plugin))
+	  ((eq compl t) (tempo-insert-template
+			 (cdr (assoc match-string
+				     collection))
+			 nil))
+	  (t (if (setq exact (assoc compl collection))
+		 (tempo-insert-template (cdr exact) nil)
+	       (insert compl)
+	       (or silent (ding))
+	       (if tempo-show-completion-buffer
+		   (tempo-display-completions match-string
+					      collection)))))))
+
+(defun tdiary-today ()
+  (let* ((offset-second (* tdiary-hour-offset 60 60))
+	 (now (current-time))
+	 (high (nth 0 now))
+	 (low (+ (nth 1 now) offset-second))
+	 (micro (nth 2 now)))
+    (setq high (+ high (/ low 65536))
+	  low (% low 65536))
+    (when (< low 0)
+      (setq high (1- high)
+	    low (+ low 65536)))
+    (list high low micro)))
+
+(defun tdiary-read-username (url name)
+  (let ((username (tdiary-passwd-cache-read-username url)))
+    (or username
+	(read-string (concat "User Name for '" name "': ")))))
+
+(defun tdiary-read-password (url name)
+  (let ((password (tdiary-passwd-cache-read-password url)))
+    (or password
+	(read-passwd (concat "Password for '" name "': ")))))
+
+(defun tdiary-read-date (date)
+  (while (not (string-match
+	       "[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]"
+	       (setq date (read-string "Date: "
+				       (or date
+					   (format-time-string "%Y%m%d" (tdiary-today)))))))
+    nil)
+  date)
+
+(defun tdiary-read-title (date)
+  (read-string (concat "Title for " date ": ") tdiary-title))
+
+(defun tdiary-read-mode (mode)
+  (let ((default (caar tdiary-edit-mode-list)))
+    (completing-read "Editing mode: " tdiary-edit-mode-list
+		   nil t (or mode default) nil default)))
+
+(defun tdiary-passwd-file-load ()
+  "Load password alist."
+  (save-excursion
+    (let ((buf (get-buffer-create "*tdiary-passwd-cache*")))
+      (when (and tdiary-passwd-file
+		 (file-readable-p tdiary-passwd-file))
+	(set-buffer buf)
+	(insert-file-contents tdiary-passwd-file)
+	(setq tdiary-passwd-cache (read buf)))
+      (kill-buffer buf))))
+
+(defun tdiary-passwd-file-save ()
+  "Save password alist.
+Dangerous!!!"
+  (interactive)
+  (save-excursion
+    (let ((buf (get-buffer-create "*tdiary-passwd-cache*")))
+      (set-buffer buf)
+      (erase-buffer)
+      (prin1 tdiary-passwd-cache buf)
+      (terpri buf)
+      (when (and tdiary-passwd-file
+		 (file-writable-p tdiary-passwd-file))
+	(write-region (point-min) (point-max) tdiary-passwd-file)
+	(set-file-modes tdiary-passwd-file tdiary-passwd-file-mode))
+      (kill-buffer buf))))
+
+(defun tdiary-passwd-cache-clear (url)
+  (setq tdiary-passwd-cache (remassoc url tdiary-passwd-cache)))
+
+(defun tdiary-passwd-cache-save (url user pass)
+  (tdiary-passwd-cache-clear url)
+  (add-to-list 'tdiary-passwd-cache
+	       (cons url (cons user (base64-encode-string pass)))))
+
+(defun tdiary-passwd-cache-read-username (url)
+  (cadr (assoc url tdiary-passwd-cache)))
+
+(defun tdiary-passwd-cache-read-password (url)
+  (let ((password (cddr (assoc url tdiary-passwd-cache))))
+    (and password
+	 (base64-decode-string password))))
+
+(defun tdiary-post (mode date data)
+  (let ((url (concat tdiary-diary-url tdiary-update-rb))
+	buf title user pass year month day post-data)
+    (when (not (equal mode "edit"))
+      (setq tdiary-edit-mode (setq mode (tdiary-read-mode mode)))
+      (setq tdiary-date (setq date (tdiary-read-date date)))
+      (setq tdiary-title (setq title (tdiary-read-title date))))
+    (setq user (tdiary-read-username url tdiary-diary-name))
+    (setq pass (tdiary-read-password url tdiary-diary-name))
+    (string-match "\\([0-9][0-9][0-9][0-9]\\)\\([0-9][0-9]\\)\\([0-9][0-9]\\)"
+		  date)
+    (setq year (match-string 1 date)
+	  month (match-string 2 date)
+	  day (match-string 3 date))
+    (add-to-list 'post-data (cons "old" date))
+    (add-to-list 'post-data (cons "year" year))
+    (add-to-list 'post-data (cons "month" month))
+    (add-to-list 'post-data (cons "day" day))
+    (if tdiary-csrf-key (add-to-list 'post-data (cons "csrf_protection_key" tdiary-csrf-key)))
+    (or (equal mode "edit")
+	(add-to-list 'post-data
+		     (cons "title" (http-url-hexify-string
+				    title
+				    tdiary-coding-system))))
+    (add-to-list 'post-data (cons mode mode))
+    (and data
+	(add-to-list 'post-data 
+		     (cons "body"
+			   (http-url-hexify-string data tdiary-coding-system))))
+    (setq buf (http-fetch url 'post user pass post-data))
+    (if (bufferp buf)
+	(save-excursion
+	  (tdiary-passwd-cache-save url user pass)
+	  (set-buffer buf)
+	  (decode-coding-region (point-min) (point-max) tdiary-coding-system)
+	  (goto-char (point-min))
+	  buf)
+      (tdiary-passwd-cache-clear url)
+      (error "tDiary POST: %s - %s" (car buf) (cdr buf)))))
+
+(defun tdiary-post-text ()
+  (let* ((dirname (expand-file-name tdiary-diary-name
+				    (or tdiary-text-directory
+					(expand-file-name "~/"))))
+	 (filename (expand-file-name (concat tdiary-date tdiary-text-suffix)
+				     dirname)))
+    (unless (file-directory-p dirname)
+      (make-directory dirname t)
+      (set-file-modes dirname tdiary-text-directory-mode))
+
+    ;; If buffer-file-name is tdiary-text-directory/tdiary-diary-name/yyyymmdd.td
+    ;; do nothing.
+    (unless (equal filename buffer-file-name)
+      (cond
+       ((equal tdiary-edit-mode "replace")
+	(write-region (point-min) (point-max) filename))
+       ((equal tdiary-edit-mode "append")
+	(write-region (point-min) (point-max) filename t))))))
+
+(defun tdiary-update ()
+  "Update diary."
+  (interactive)
+  (unless (and (eq (char-before (point-max)) ?\n)
+	       (eq (char-before (1- (point-max))) ?\n))
+    (save-excursion
+      (goto-char (point-max))
+      (insert "\n")))
+  (tdiary-post tdiary-edit-mode tdiary-date
+	       (buffer-substring (point-min) (point-max)))
+  (when tdiary-text-save-p
+    (tdiary-post-text))
+  (if buffer-file-name
+      (save-buffer)
+    (set-buffer-modified-p nil))
+  (message "SUCCESS")
+  (and (functionp tdiary-browser-function)
+       (funcall tdiary-browser-function
+		(concat tdiary-diary-url tdiary-index-rb
+			"?date=" tdiary-date))))
+
+(defun tdiary-do-replace-entity-ref (from to &optional str)
+  (save-excursion
+    (goto-char (point-min))
+    (if (stringp str)
+	(progn
+	  (while (string-match from str)
+	    (setq str (replace-match to nil nil str)))
+	  str)
+      (while (search-forward from nil t)
+	(replace-match to nil nil)))))
+
+(defun tdiary-replace-entity-refs (&optional str)
+  "Replace entity references.
+
+If STR is a string, replace entity references within the string.
+Otherwise replace all entity references within current buffer."
+  (tdiary-do-replace-entity-ref
+   "&amp;" "&"	 
+   (tdiary-do-replace-entity-ref
+    "&lt;" "<"
+    (tdiary-do-replace-entity-ref
+     "&gt;" ">"
+     (tdiary-do-replace-entity-ref "&quot;" "\"" str)))))
+
+;(defun tdiary-read-mode (mode)
+;  (let ((default (caar tdiary-edit-mode-list)))
+;    (completing-read "Editing mode: " tdiary-edit-mode-list
+;		   nil t (or mode default) nil default)))
+
+
+(defun tdiary-obsolete-check ()
+  ;; setting tdiary-diary-url in tdiary-init-file is obsolete.
+  (when tdiary-diary-url
+    (message "tdiary-diary-url is OBSOLETE.  Use tdiary-diary-list instead of tdiary-diary-url.")
+    (sit-for 5)))
+
+;(defun tdiary-setup-diary-url (select-url)
+;  (interactive)
+;  (if (interactive-p)
+;      (setq select-url t))
+(defun tdiary-setup-diary-url (select-url)
+  ;; setting tdiary-diary-url in tdiary-init-file is obsolete.
+  (tdiary-obsolete-check)
+  (unless tdiary-diary-url
+    (let* ((selected (car tdiary-diary-list))
+	   (default (car selected)))
+      (when select-url
+	(setq selected (assoc (completing-read "Select SITE: " tdiary-diary-list
+					       nil t default nil default)
+			      tdiary-diary-list)))
+      (setq tdiary-diary-name (nth 0 selected)
+	    tdiary-diary-url (nth 1 selected))
+      (and (eq (length selected) 4)
+	   (setq tdiary-index-rb (nth 2 selected)
+		 tdiary-update-rb (nth 3 selected))))))
+
+(defun tdiary-new-or-replace (replacep select-url)
+  (let (buf)
+    (setq buf (generate-new-buffer "*tdiary tmp*"))
+    (switch-to-buffer buf)
+    (tdiary-mode)
+    (setq tdiary-edit-mode "append")
+    (let (start end body title csrf-key)
+      (save-excursion
+	(setq buf (tdiary-post "edit" tdiary-date nil))
+	(when (bufferp buf)
+	  (save-excursion
+	    (set-buffer buf)
+	    (if (re-search-forward "<input [^>]+name=\"csrf_protection_key\" [^>]*value=\"\\([^>\"]*\\)\">" nil t nil)
+		(setq csrf-key (match-string 1)))
+	    (re-search-forward "<input [^>]+name=\"title\" [^>]+value=\"\\([^>\"]*\\)\">" nil t nil)
+	    (setq title (match-string 1))
+	    (re-search-forward "<textarea [^>]+>" nil t nil)
+	    (setq start (match-end 0))
+	    (re-search-forward "</textarea>" nil t nil)
+	    (setq end (match-beginning 0))
+	    (setq body (buffer-substring start end))
+	    )
+	  (setq tdiary-csrf-key (and csrf-key (tdiary-replace-entity-refs csrf-key)))
+	  (when replacep
+	    (insert body)
+	    (setq tdiary-edit-mode "replace")
+	    (setq tdiary-title (tdiary-replace-entity-refs title))
+	    (goto-char (point-min))
+	    (tdiary-replace-entity-refs)
+	    (set-buffer-modified-p nil)))))))
+
+(defun tdiary-new (&optional select-url)
+  (interactive "P")
+  (tdiary-new-or-replace nil select-url))
+
+(defun tdiary-replace (&optional select-url)
+  (interactive "P")
+  (tdiary-new-or-replace t select-url))
+
+(defvar tdiary-mode-map (make-sparse-keymap)
+  "Set up keymap for tdiary-mode.
+If you want to set up your own key bindings, use `tdiary-mode-hook'.")
+
+(define-key tdiary-mode-map [(control return)] 'tdiary-complete-plugin)
+(define-key tdiary-mode-map "\C-c\C-c" 'tdiary-update)
+
+(push (cons 'tdiary-date tdiary-mode-map) minor-mode-map-alist)
+(if (boundp 'minor-mode-list) (push 'tdiary-mode minor-mode-list))
+(push '(tdiary-date " tDiary") minor-mode-alist)
+
+(defun tdiary-load-init-file ()
+  "Load init file."
+  (let ((init-file (expand-file-name tdiary-init-file)))
+    (when (file-readable-p init-file)
+      (load init-file t t))))
+
+(defun tdiary-make-temp-file-name ()
+  (let ((tmpdir (expand-file-name tdiary-diary-name
+				  (expand-file-name (user-login-name)
+						    temporary-file-directory))))
+    (unless (file-directory-p tmpdir)
+      (make-directory tmpdir t)
+      (set-file-modes tmpdir tdiary-text-directory-mode))
+    (expand-file-name tdiary-date tmpdir)))
+
+(defun tdiary-html-mode-init ()
+  "Initialize tDiary for default style"
+  (tdiary-tempo-define (append tdiary-plugin-initial-definition
+			       tdiary-plugin-definition))
+  (tempo-use-tag-list 'tdiary-tempo-tags tdiary-completion-finder))
+
+(defun tdiary-rd-mode-init ()
+  "Initialize tDiary for RD style"
+  )
+
+(defun tdiary-mode ()
+  "tDiary editing mode.
+The value of `tdiary-style-mode' will be used as actual major mode.
+
+\\{tdiary-mode-map}"
+  (interactive)
+  (funcall tdiary-style-mode)
+  (and (featurep 'font-lock)
+       (font-lock-set-defaults))
+  (tdiary-mode-setup))
+
+(defun tdiary-mode-setup ()
+  "Set tDiary mode up."
+  (interactive)
+  (make-local-variable 'require-final-newline)	
+  (make-local-variable 'tdiary-date)
+  (make-local-variable 'tdiary-title)
+  (make-local-variable 'tdiary-edit-mode)
+  (make-local-variable 'tdiary-diary-url)
+  (make-local-variable 'tdiary-index-rb)
+  (make-local-variable 'tdiary-update-rb)
+  (make-local-variable 'tdiary-csrf-key)
+  (setq require-final-newline t
+	indent-tabs-mode nil
+	tdiary-edit-mode "replace"
+	tdiary-date (format-time-string "%Y%m%d" (tdiary-today)))
+
+  (tdiary-load-init-file)
+
+  (tdiary-setup-diary-url (if (boundp 'select-url)
+			      select-url
+			    t))
+
+  (set-buffer-file-coding-system tdiary-coding-system)
+
+  (or tdiary-passwd-cache
+      (tdiary-passwd-file-load))
+
+  (if buffer-file-name
+      (let ((buf-name (file-name-nondirectory buffer-file-name)))
+	(when (string-match
+	       "\\([0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]\\)"
+	       buf-name)
+	  (setq tdiary-date (match-string 1 buf-name))))
+    (setq tdiary-date (tdiary-read-date nil))
+    (if tdiary-text-save-p
+	(set-visited-file-name (tdiary-make-temp-file-name))
+      (unless (string= (buffer-name) tdiary-date)
+	(rename-buffer tdiary-date t))))
+
+  (let ((init (intern (concat "tdiary-" (symbol-name tdiary-style-mode) "-init"))))
+    (if (fboundp init) (funcall init)))
+
+  (run-hooks 'tdiary-mode-hook))
+
+(defun tdiary-mode-toggle (&optional arg)
+  (interactive "P")
+  (let ((in-tdiary (and (boundp 'tdiary-date) tdiary-date)))
+    (cond ((not arg)
+	   (setq arg (not in-tdiary)))
+	  ((or (eq arg '-) (and (numberp arg) (< arg 0)))
+	   (setq arg nil)))
+    (cond (arg
+	   (tdiary-mode-setup))
+	  (in-tdiary
+	   (setq tdiary-date nil)))))
+
+(provide 'tdiary-mode)
+;;; tdiary-mode.el ends here
Index: /tdiary/branches/upstream/contrib/util/tdiary-mode/http.el
===================================================================
--- /tdiary/branches/upstream/contrib/util/tdiary-mode/http.el (revision 710)
+++ /tdiary/branches/upstream/contrib/util/tdiary-mode/http.el (revision 710)
@@ -0,0 +1,130 @@
+;;; http.el -- utils for HTTP
+
+;; Copyright (C) 2002 Junichiro Kita
+
+;; Author: Junichiro Kita <kita@kitaj.no-ip.com>
+
+;; $Id: http.el,v 1.3 2005/07/20 08:39:56 tadatadashi Exp $
+;;
+;; This program is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation; either version 2, or (at
+;; your option) any later version.
+
+;; This program is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;; General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'pces)
+
+(defvar http-proxy-server nil "Proxy server for HTTP.")
+(defvar http-proxy-port   nil "Proxy port for HTTP.")
+
+(defvar http-timeout 10
+  "Timeout for HTTP.")
+
+;; derived from url.el
+(defconst http-url-unreserved-chars
+  '(
+    ?a ?b ?c ?d ?e ?f ?g ?h ?i ?j ?k ?l ?m ?n ?o ?p ?q ?r ?s ?t ?u ?v ?w ?x ?y ?z
+    ?A ?B ?C ?D ?E ?F ?G ?H ?I ?J ?K ?L ?M ?N ?O ?P ?Q ?R ?S ?T ?U ?V ?W ?X ?Y ?Z
+    ?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9
+    ?$ ?- ?_ ?. ?! ?~ ?* ?' ?\( ?\) ?,)
+  "A list of characters that are _NOT_ reserve in the URL spec.
+This is taken from draft-fielding-url-syntax-02.txt - check your local
+internet drafts directory for a copy.")
+
+;; derived from url.el
+(defun http-url-hexify-string (str coding)
+  "Escape characters in a string.
+At first, encode STR using CODING, then url-hexify."
+  (mapconcat
+   (function
+    (lambda (char)
+      (if (not (memq char http-url-unreserved-chars))
+          (if (< char 16)
+              (upcase (format "%%0%x" char))
+            (upcase (format "%%%x" char)))
+        (char-to-string char))))
+   (encode-coding-string str coding) ""))
+
+(defvar http-fetch-terminator "</body>"
+  "content body end mark.")
+(defun http-fetch (url method &optional user pass data)
+  "Fetch via HTTP.
+
+URL is a url to be POSTed.
+METHOD is 'get or 'post.
+USER and PASS must be a valid username and password, if required.
+DATA is an alist, each element is in the form of (FIELD . DATA).
+
+If no error, return a buffer which contains output from the web server.
+If error, return a cons cell (ERRCODE . DESCRIPTION)."
+  (let (connection server port path buf str len)
+    (string-match "^http://\\([^/:]+\\)\\(:\\([0-9]+\\)\\)?\\(/.*$\\)" url)
+    (setq server (match-string 1 url)
+          port (string-to-int (or (match-string 3 url) "80"))
+          path (if http-proxy-server url (match-string 4 url)))
+    (setq str (mapconcat
+               '(lambda (x)
+                  (concat (car x) "=" (cdr x)))
+               data "&"))
+    (setq len (length str))
+    (save-excursion
+      (setq buf (get-buffer-create (concat "*result from " server "*")))
+      (set-buffer buf)
+      (erase-buffer)
+      (setq connection
+            (as-binary-process
+             (open-network-stream (concat "*request to " server "*")
+                                  buf
+                                  (or http-proxy-server server)
+                                  (or http-proxy-port port))))
+      (process-send-string
+       connection
+       (concat (if (eq method 'post)
+                   (concat "POST " path)
+                 (concat "GET " path (if (> len 0)
+                                         (concat "?" str))))
+               " HTTP/1.0\r\n"
+               (concat "Host: " server "\r\n")
+               "Connection: close\r\n"
+               "Referer: " url "\r\n"
+               "Content-type: application/x-www-form-urlencoded\r\n"
+               (if (and user pass)
+                   (concat "Authorization: Basic "
+                           (base64-encode-string
+                            (concat user ":" pass))
+                           "\r\n"))
+               (if (eq method 'post)
+                   (concat "Content-length: " (int-to-string len) "\r\n"
+                           "\r\n"
+                           str))
+               "\r\n"))
+      (goto-char (point-min))
+      (while (not (search-forward http-fetch-terminator nil t))
+        (unless (accept-process-output connection http-timeout)
+          (error "HTTP fetch: Connection timeout!"))
+        (goto-char (point-min)))
+      (goto-char (point-min))
+      (save-excursion
+        (if (re-search-forward "HTTP/1.[01] \\([0-9][0-9][0-9]\\) \\(.*\\)" nil t)
+            (let ((code (match-string 1))
+                  (desc (match-string 2)))
+              (cond ((equal code "200")
+                     buf)
+                    (t
+                     (cons code desc)))))))))
+
+(provide 'http)
+;;; http.el ends here
Index: /tdiary/branches/upstream/contrib/plugin/add_bookmark.rb
===================================================================
--- /tdiary/branches/upstream/contrib/plugin/add_bookmark.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/plugin/add_bookmark.rb (revision 710)
@@ -0,0 +1,86 @@
+# add_bookmark.rb $Revision 1.3 $
+#
+# Copyright (c) 2005 SHIBATA Hiroshi <h-sbt@nifty.com>
+# You can redistribute it and/or modify it under GPL2.
+
+def bookmark_init
+	@conf['add.bookmark.delicious'] ||= ""
+	@conf['add.bookmark.hatena'] ||= ""
+	@conf['add.bookmark.livedoor'] ||= ""
+	@conf['add.bookmark.buzzurl'] ||= ""
+end
+
+add_subtitle_proc do |date, index, subtitle|
+	bookmark_init
+	
+	if @conf.mobile_agent? then
+		caption = %Q|#{subtitle}|
+	else
+		caption = %Q|#{subtitle} |
+
+		section_url = @conf.base_url + anchor(date.strftime('%Y%m%d')) + '#p' + ('%02d' % index)
+		
+		if @conf['add.bookmark.delicious'] == "t" then
+			caption += %Q|<a href="http://del.icio.us/post/v4?url=#{h(section_url)}">|
+			caption += %Q|<img src="http://images.del.icio.us/static/img/delicious.small.gif" width="10" height="10" style="border: none;vertical-align: middle;" alt="#{@caption_delicious}" title="#{@caption_delicious}" />|
+			caption += %Q|</a> |
+		end
+
+		if @conf['add.bookmark.hatena'] == "t" then
+			caption += %Q|<a href="http://b.hatena.ne.jp/append?#{h(section_url)}">|
+			caption += %Q|<img src="http://b.hatena.ne.jp/images/append.gif" width="16" height="12" style="border: none;vertical-align: middle;" alt="#{@caption_hatena}" title="#{@caption_hatena}" />|
+			caption += %Q|</a> |
+		end
+			
+		if @conf['add.bookmark.livedoor'] == "t" then
+			caption += %Q|<a href="http://clip.livedoor.com/redirect?link=#{h(section_url)}" class="ldclip-redirect">|
+			caption += %Q|<img src="http://parts.blog.livedoor.jp/img/cmn/clip_16_16_w.gif" width="16" height="16" style="border: none;vertical-align: middle;" alt="#{@caption_livedoor}" title="#{@caption_livedoor}" />|
+			caption += %Q|</a> |
+		end
+
+		if @conf['add.bookmark.buzzurl'] == "t" then
+			caption += %Q|<a href="http://buzzurl.jp/entry/#{h(section_url)}">|
+			caption += %Q|<img src="http://buzzurl.jp/static/image/api/icon/add_icon_mini_10.gif" width="16" height="12" style="border: none;vertical-align: middle;" title="#{@caption_buzzurl}" alt="#{@caption_buzzurl}" class="icon" />|
+			caption += %Q|</a> |
+		end
+	end
+	
+	<<-HTML
+	#{caption}
+	HTML
+end
+
+add_conf_proc( 'add_bookmark', @add_bookmark_label ) do
+	add_bookmark_conf_proc
+end
+
+def add_bookmark_conf_proc
+	bookmark_init
+	saveconf_add_bookmark
+
+	bookmark_categories = [
+	'add.bookmark.delicious',
+	'add.bookmark.hatena',
+	'add.bookmark.livedoor',
+	'add.bookmark.buzzurl'
+	]
+
+	r = ''
+	r << %Q|<h3 class="subtitle">#{@add_bookmark_label}</h3><p>#{@add_bookmark_desc}</p><ul>|
+
+	bookmark_categories.each_with_index do |idx,view|
+		checked = "t" == @conf[idx] ? ' checked' : ''
+		label = @bookmark_label[view]
+		r << %Q|<li><label for="#{idx}"><input id=#{idx} name=#{idx} type="checkbox" value="t"#{checked}>#{label}</label></li>|
+	end
+	r << %Q|</ul>|
+end
+
+if @mode == 'saveconf'
+	def saveconf_add_bookmark
+		@conf['add.bookmark.delicious'] = @cgi.params['add.bookmark.delicious'][0]
+		@conf['add.bookmark.hatena'] = @cgi.params['add.bookmark.hatena'][0]
+		@conf['add.bookmark.livedoor'] = @cgi.params['add.bookmark.livedoor'][0]
+		@conf['add.bookmark.buzzurl'] = @cgi.params['add.bookmark.buzzurl'][0]
+	end
+end
Index: /tdiary/branches/upstream/contrib/plugin/google_adsense.rb
===================================================================
--- /tdiary/branches/upstream/contrib/plugin/google_adsense.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/plugin/google_adsense.rb (revision 710)
@@ -0,0 +1,107 @@
+#
+# Google AdSense plugin for tDiary
+#
+# Copyright (C) 2004 Kazuhiko <kazuhiko@fdiary.net>
+# You can redistribute it and/or modify it under GPL2.
+#
+# modified by TADA Tadashi <sho@spc.gr.jp>
+#
+def google_adsense( layout = nil )
+	google_adsense_init( layout )
+	google_ad_client = "pub-3317603667498586"
+	google_ad_size = [
+		[468, 60],	# 0
+		[120, 600],	# 1
+		[728, 90],	# 2
+		[300, 250],	# 3
+		[125, 125],	# 4
+		[160, 600],	# 5
+		[120, 240],	# 6
+		[180, 150], # 7
+		[250, 250], # 8
+		[336, 280]  # 9
+	]
+	<<-EOF
+<script type="text/javascript"><!--
+google_ad_client = "#{google_ad_client}";
+google_alternate_ad_url = ""
+google_ad_width = #{google_ad_size[@conf['google_adsense.layout']][0]};
+google_ad_height = #{google_ad_size[@conf['google_adsense.layout']][1]};
+google_ad_format = "#{google_ad_size[@conf['google_adsense.layout']][0]}x#{google_ad_size[@conf['google_adsense.layout']][1]}_as";
+google_color_border = "#{h @conf['google_adsense.color.border']}";
+google_color_bg = "#{h @conf['google_adsense.color.bg']}";
+google_color_link = "#{h @conf['google_adsense.color.link']}";
+google_color_url = "#{h @conf['google_adsense.color.url']}";
+google_color_text = "#{h @conf['google_adsense.color.text']}";
+//--></script>
+<script type="text/javascript"
+	src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
+</script>
+  EOF
+end
+
+def google_adsense_init( layout )
+	if layout != nil then
+		@conf['google_adsense.layout'] = layout.to_i
+	else
+		@conf['google_adsense.layout'] = 0 unless @conf['google_adsense.layout']
+	end
+	@conf['google_adsense.layout'] = @conf['google_adsense.layout'].to_i
+	if @conf['google_adsense.layout'] < 0 or @conf['google_adsense.layout'] > 9 then
+		@conf['google_adsense.layout'] = 0
+	end
+
+	@conf['google_adsense.color.border'] = 'CCCCCC' unless @conf['google_adsense.color.border']
+	@conf['google_adsense.color.bg'] = 'FFFFFF' unless @conf['google_adsense.color.bg']
+	@conf['google_adsense.color.link'] = '000000' unless @conf['google_adsense.color.link']
+	@conf['google_adsense.color.url'] = '666666' unless @conf['google_adsense.color.url']
+	@conf['google_adsense.color.text'] = '333333' unless @conf['google_adsense.color.text']
+end
+
+# insert section target tags
+add_body_enter_proc do |date|
+	"<!-- google_ad_section_start -->\n"
+end
+add_body_leave_proc do |date|
+	"<!-- google_ad_section_end -->\n"
+end
+
+add_conf_proc( 'google_adsense', 'Google AdSense' ) do
+	if @mode == 'saveconf' then
+		@conf['google_adsense.layout'] = @cgi.params['google_adsense.layout'][0].to_i
+		@conf['google_adsense.color.border'] = @cgi.params['google_adsense.color.border'][0]
+		@conf['google_adsense.color.bg'] = @cgi.params['google_adsense.color.bg'][0]
+		@conf['google_adsense.color.link'] = @cgi.params['google_adsense.color.link'][0]
+		@conf['google_adsense.color.url'] = @cgi.params['google_adsense.color.url'][0]
+		@conf['google_adsense.color.text'] = @cgi.params['google_adsense.color.text'][0]
+	else
+		google_adsense_init( nil )
+	end
+
+	<<-HTML
+	<h3>バナーのサイズ(#{@conf['google_adsense.layout']})</h3>
+	<p>広告バナーのサイズは全部で7種類あります。お好きなサイズを選んでください。</p>
+	<p><select name="google_adsense.layout">
+		<option value="0"#{' selected' if @conf['google_adsense.layout'] == 0}>横長小・広告2つ(468, 60)</option>
+		<option value="2"#{' selected' if @conf['google_adsense.layout'] == 2}>横長大・広告4つ(728, 90)</option>
+		<option value="4"#{' selected' if @conf['google_adsense.layout'] == 4}>方形微小・広告1つ(125, 125)</option>
+		<option value="7"#{' selected' if @conf['google_adsense.layout'] == 7}>方形小・広告1つ(180, 150)</option>
+		<option value="8"#{' selected' if @conf['google_adsense.layout'] == 8}>方形中・広告3つ(250, 250)</option>
+		<option value="3"#{' selected' if @conf['google_adsense.layout'] == 3}> 方形大・広告4つ(300, 250)</option>
+		<option value="9"#{' selected' if @conf['google_adsense.layout'] == 9}> 方形特大・広告4つ(336, 280)</option>
+		<option value="6"#{' selected' if @conf['google_adsense.layout'] == 6}> 縦長小・広告2つ(120, 240)</option>
+		<option value="1"#{' selected' if @conf['google_adsense.layout'] == 1}> 縦長中・広告4つ(120, 600)</option>
+		<option value="5"#{' selected' if @conf['google_adsense.layout'] == 5}> 縦長大・広告5つ(160, 600)</option>
+	</select></p>
+	<h3>バナーの色</h3>
+	<p>バナーの各パーツの色を指定できます。HTMLやCSSと同じ、6桁の16進数で指定します。</p>
+	<table style="margin-left: 2em;">
+		<tr><td>枠</td><td style="background-color: ##{h @conf['google_adsense.color.border']};">&nbsp;<input name="google_adsense.color.border" size="7" value="#{h @conf['google_adsense.color.border']}">&nbsp;</td></tr>
+		<tr><td>背景</td><td style="background-color: ##{h @conf['google_adsense.color.bg']};">&nbsp;<input name="google_adsense.color.bg" size="7" value="#{h @conf['google_adsense.color.bg']}">&nbsp;</td></tr>
+		<tr><td>リンク</td><td style="background-color: ##{h @conf['google_adsense.color.link']};">&nbsp;<input name="google_adsense.color.link" size="7" value="#{h @conf['google_adsense.color.link']}">&nbsp;</td></tr>
+		<tr><td>URL</td><td style="background-color: ##{h @conf['google_adsense.color.url']};">&nbsp;<input name="google_adsense.color.url" size="7" value="#{h @conf['google_adsense.color.url']}">&nbsp;</td></tr>
+		<tr><td>テキスト</td><td style="background-color: ##{h @conf['google_adsense.color.text']};">&nbsp;<input name="google_adsense.color.text" size="7" value="#{h @conf['google_adsense.color.text']}">&nbsp;</td></tr>
+	</table>
+	HTML
+end
+
Index: /tdiary/branches/upstream/contrib/plugin/openid.rb
===================================================================
--- /tdiary/branches/upstream/contrib/plugin/openid.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/plugin/openid.rb (revision 710)
@@ -0,0 +1,91 @@
+#
+# openid.rb: Insert OpenID delegation information. $Revision: 1.10 $
+#
+# Copyright (C) 2005, TADA Tadashi <sho@spc.gr.jp>
+# You can redistribute it and/or modify it under GPL2.
+#
+
+@openid_config = (Struct.const_defined?("OpenIdConfig") ? Struct::OpenIdConfig :
+	Struct.new("OpenIdConfig", :openid, :openid2, :x_xrds_location))
+
+if /^(?:latest|conf|saveconf)$/ =~ @mode then
+	@openid_list = {
+		# service => @openid_config.new(
+		#    [openid.server, openid.delegate(replace <ID> as account name)],   # openid
+		#    [openid2.provider, openid2.local_id(replace <ID> as account name)], # openid2
+		#    'X-XRDS-Location(replace <ID> as account name)'),
+		'Hatena' => @openid_config.new(['https://www.hatena.ne.jp/openid/server', 'http://www.hatena.ne.jp/<ID>/']),
+		'livedoor' => @openid_config.new(['http://auth.livedoor.com/openid/server', 'http://profile.livedoor.com/<ID>']),
+		'LiveJournal' => @openid_config.new(['http://www.livejournal.com/openid/server.bml', 'http://<ID>.livejournal.com/']),
+		'OpenID.ne.jp' => @openid_config.new(
+			['http://www.openid.ne.jp/user/auth', 'http://<ID>.openid.ne.jp'],
+			nil,
+			'http://<ID>.openid.ne.jp/user/xrds'),
+		'TypeKey' => @openid_config.new(['http://www.typekey.com/t/openid/', 'http://profile.typekey.com/<ID>/']),
+		'Videntity.org' => @openid_config.new(['http://videntity.org/serverlogin?action=openid', 'http://<ID>.videntity.org/']),
+		'Vox' => @openid_config.new(['http://www.vox.com/services/openid/server', 'http://<ID>.vox.com/']),
+		'myopenid.com' => @openid_config.new(
+			['http://www.myopenid.com/server', 'http://<ID>.myopenid.com'], # openid
+			['http://www.myopenid.com/server', 'http://<ID>.myopenid.com'], # openid2
+			"http://www.myopenid.com/xrds?username=<ID>"),
+		'claimID.com' => @openid_config.new(
+			['http://openid.claimid.com/server', 'http://openid.claimid.com/<ID>'],
+			nil, #['http://openid.claimid.com/server', 'http://openid.claimid.com/<ID>'],
+			'http://claimid.com/<ID>/xrds'),
+		'Personal Identity Provider (PIP)' => @openid_config.new(
+			['http://pip.verisignlabs.com/server', 'http://<ID>.pip.verisignlabs.com/'],
+			['http://pip.verisignlabs.com/server', 'http://<ID>.pip.verisignlabs.com/'],
+			'http://pip.verisignlabs.com/user/<ID>/yadisxrds'),
+		'Yahoo! Japan' => @openid_config.new(
+			nil,
+			['https://open.login.yahooapis.jp/openid/op/auth', 'https://me.yahoo.co.jp/a/<ID>'],
+			'http://open.login.yahoo.co.jp/openid20/www.yahoo.co.jp/xrds'),
+		'Yahoo!' => @openid_config.new(
+			nil,
+			['https://open.login.yahooapis.com/openid/op/auth', 'https://me.yahoo.com/a/<ID>'],
+			'http://open.login.yahooapis.com/openid20/www.yahoo.com/xrds'),
+	}
+
+	if @conf['openid.service'] and @conf['openid.id'] then
+		openid_service = @openid_list[@conf['openid.service']]
+		openid_id = @conf['openid.id']
+		result = ''
+		add_header_proc do
+			result = <<-HTML if openid_service.openid
+			<link rel="openid.server" href="#{h openid_service.openid[0]}">
+			<link rel="openid.delegate" href="#{h openid_service.openid[1].sub( /<ID>/, openid_id )}">
+			HTML
+			result << <<-HTML if openid_service.openid2
+			<link rel="openid2.provider" href="#{h openid_service.openid2[0]}">
+			<link rel="openid2.local_id" href="#{h openid_service.openid2[1].sub( /<ID>/, openid_id )}">
+			HTML
+			result << <<-HTML if openid_service.x_xrds_location
+			<meta http-equiv="X-XRDS-Location" content="#{h openid_service.x_xrds_location.sub( /<ID>/, openid_id )}">
+			HTML
+			result.gsub( /^\t{2}/, '' )
+		end
+	end
+end
+
+add_conf_proc( 'openid', @openid_conf_label ) do
+	if @mode == 'saveconf' then
+		@conf['openid.service'] = @cgi.params['openid.service'][0]
+		@conf['openid.id'] = @cgi.params['openid.id'][0]
+	end
+
+	options = ''
+	@openid_list.each_key do |key|
+		options << %Q|<option value="#{h key}"#{" selected" if @conf['openid.service'] == key}>#{h key}</option>\n|
+	end
+	<<-HTML
+	<h3 class="subtitle">#{@openid_service_label}</h3>
+	<p>#{@openid_service_desc}</p>
+	<p><select name="openid.service">
+		#{options}
+	</select></p>
+
+	<h3 class="subtitle">#{@openid_id_label}</h3>
+	<p>#{@openid_id_desc}</p>
+	<p><input name="openid.id" value="#{h @conf['openid.id']}"></p>
+	HTML
+end
Index: /tdiary/branches/upstream/contrib/plugin/recent_estraier.rb
===================================================================
--- /tdiary/branches/upstream/contrib/plugin/recent_estraier.rb (revision 710)
+++ /tdiary/branches/upstream/contrib/plugin/recent_estraier.rb (revision 710)
@@ -0,0 +1,50 @@
+# recent_estraier.rb $Revision 1.1 $
+#
+# recent_estraier: Estraier検索語新しい順
+# 		 estsearch.cgiが作成する検索キーワードログから
+#		 最新xx件分の検索語を表示します。
+# パラメタ:
+#   file:       検索キーワードログファイル名(絶対パス表記) 
+#   estraier:     estseach.cgiのパス 
+#   limit:      表示件数(未指定時:5) 
+#   make_link:  <a>を生成するか?(未指定時:生成する)    
+#
+#
+# Copyright (c) 2005 SHIBATA Hiroshi <h-sbt@nifty.com>
+# Distributed under the GPL2.
+#
+
+require 'nkf'
+
+def recent_estraier(file, estraier, limit = 5, make_link = true)
+   begin
+      lines = []
+  