automatic 12.3.0 → 12.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +1 -0
- data/README.md +54 -36
- data/Rakefile +14 -0
- data/VERSION +1 -1
- data/automatic.gemspec +17 -5
- data/bin/automatic +37 -3
- data/bin/automatic-config +77 -0
- data/config/default.yml +9 -12
- data/doc/ChangeLog +32 -8
- data/doc/PLUGINS +205 -0
- data/doc/PLUGINS.ja +2 -3
- data/doc/README +488 -0
- data/doc/README.ja +195 -131
- data/lib/automatic/feed_parser.rb +1 -9
- data/lib/automatic/log.rb +1 -9
- data/lib/automatic/opml.rb +239 -0
- data/lib/automatic/pipeline.rb +16 -10
- data/lib/automatic/recipe.rb +3 -4
- data/lib/automatic.rb +32 -38
- data/lib/config/validator.rb +83 -0
- data/plugins/custom_feed/svn_log.rb +1 -1
- data/plugins/filter/ignore.rb +9 -1
- data/plugins/notify/ikachan.rb +7 -6
- data/plugins/publish/hatena_bookmark.rb +6 -9
- data/script/build +63 -0
- data/spec/lib/automatic/pipeline_spec.rb +55 -0
- data/spec/lib/automatic_spec.rb +77 -0
- data/spec/lib/pipeline_spec.rb +67 -0
- data/spec/plugins/filter/ignore_spec.rb +16 -0
- data/spec/plugins/filter/image_spec.rb +4 -4
- data/spec/plugins/filter/tumblr_resize_spec.rb +4 -4
- data/spec/plugins/notify/ikachan_spec.rb +30 -0
- data/spec/plugins/publish/console_spec.rb +1 -2
- data/spec/plugins/publish/hatena_bookmark_spec.rb +36 -1
- data/spec/plugins/store/full_text_spec.rb +0 -2
- data/spec/plugins/store/permalink_spec.rb +0 -1
- data/spec/plugins/store/target_link_spec.rb +0 -1
- data/spec/plugins/subscription/feed_spec.rb +0 -1
- data/spec/spec_helper.rb +6 -4
- data/spec/user_dir/plugins/store/mock.rb +12 -0
- data/test/fixtures/sampleOPML.xml +11 -0
- data/test/integration/test_activerecord.yml +2 -2
- data/test/integration/test_fulltext.yml +3 -3
- data/test/integration/test_hatenabookmark.yml +6 -2
- data/test/integration/test_ignore.yml +4 -1
- data/test/integration/test_ignore2.yml +1 -4
- data/test/integration/test_image2local.yml +3 -5
- data/test/integration/test_svnlog.yml +2 -1
- data/test/integration/test_tumblr2local.yml +3 -3
- metadata +43 -22
- data/utils/auto_discovery.rb +0 -18
- data/utils/opml_parser.rb +0 -247
data/doc/README.ja
CHANGED
@@ -57,6 +57,121 @@ $ automatic -c config/feed2console.yml
|
|
57
57
|
本ソフトウェアの動作確認に使うことができる。
|
58
58
|
|
59
59
|
|
60
|
+
==========
|
61
|
+
レシピとは
|
62
|
+
==========
|
63
|
+
|
64
|
+
Automatic Ruby では YAML 形式で書かれた設定ファイル
|
65
|
+
を解析し、呼び出すプラグインの情報とそれに伴う様々な
|
66
|
+
情報を読み込む。
|
67
|
+
|
68
|
+
この YAML ファイルをレシピと呼ぶ。
|
69
|
+
|
70
|
+
automatic.rb 起動時に -c オプションの引数にレシピの
|
71
|
+
ファイル名を指定することができる。オプションが省略
|
72
|
+
された場合は config/default.yml が呼ばれる。
|
73
|
+
|
74
|
+
[起動例]
|
75
|
+
$ automatic -c ~/recipes/your_recipe.yml
|
76
|
+
|
77
|
+
|
78
|
+
================
|
79
|
+
レシピの記述方法
|
80
|
+
================
|
81
|
+
|
82
|
+
レシピには暗黙的な命名規則がある。
|
83
|
+
|
84
|
+
[書式]
|
85
|
+
|
86
|
+
plugins:
|
87
|
+
- module: プラグイン名
|
88
|
+
config:
|
89
|
+
変数
|
90
|
+
|
91
|
+
|
92
|
+
以下はレシピのサンプルとなる YAML ファイルである。
|
93
|
+
|
94
|
+
plugins:
|
95
|
+
- module: SubscriptionFeed
|
96
|
+
config:
|
97
|
+
feeds:
|
98
|
+
- http://reretlet.tumblr.com/rss
|
99
|
+
|
100
|
+
- module: StorePermalink
|
101
|
+
config:
|
102
|
+
db: tumblr.db
|
103
|
+
|
104
|
+
- module: FilterImage
|
105
|
+
|
106
|
+
- module: FilterTumblrResize
|
107
|
+
|
108
|
+
- module: StoreTargetLink
|
109
|
+
config:
|
110
|
+
path: /Users/yourname/Desktop/
|
111
|
+
interval: 1
|
112
|
+
|
113
|
+
このサンプルレシピの例では
|
114
|
+
1. Subscription Feed で Tumblr のフィードを購読し
|
115
|
+
2. StorePermalink でパーマリンクをデータベースに保存し
|
116
|
+
3 FilterImage と FilterTumblrResize で画像の URL を指定して
|
117
|
+
4. StoreTargetLink で Tumblr の画像をダウンロードする
|
118
|
+
という一連の処理をプラグインの組み合わせで実現している。
|
119
|
+
|
120
|
+
|
121
|
+
以下に別の例を示す。
|
122
|
+
|
123
|
+
plugins:
|
124
|
+
- module: SubscriptionFeed
|
125
|
+
config:
|
126
|
+
feeds:
|
127
|
+
- http://example.com/rss2
|
128
|
+
- http://hogefuga.com/feed
|
129
|
+
|
130
|
+
- module: FilterIgnore
|
131
|
+
config:
|
132
|
+
link:
|
133
|
+
- hoge
|
134
|
+
- fuga
|
135
|
+
|
136
|
+
- module: StorePermalink
|
137
|
+
config:
|
138
|
+
db: permalink.db
|
139
|
+
|
140
|
+
- module: PublishHatenaBookmark
|
141
|
+
config:
|
142
|
+
username: your_hatena_id
|
143
|
+
password: your_password
|
144
|
+
interval: 5
|
145
|
+
|
146
|
+
このサンプルレシピの例では
|
147
|
+
1. Subscription Feed でフィードを購読し
|
148
|
+
2. FilterIgnore で無視キーワードを含む URL を除外し
|
149
|
+
3. StorePermalink でパーマリンクをデータベースに保存し
|
150
|
+
4. PublishHatenaBookmark ではてなブックマークをする
|
151
|
+
という一連の処理をプラグインの組み合わせで実現している。
|
152
|
+
|
153
|
+
|
154
|
+
このように、プラグインと設定情報をレシピに記述するだけで
|
155
|
+
複数のプラグインの組み合わせ次第で無限の可能性を実現する
|
156
|
+
ことができる。
|
157
|
+
|
158
|
+
|
159
|
+
==========
|
160
|
+
プラグイン
|
161
|
+
==========
|
162
|
+
|
163
|
+
プラグインは後述する plugins ディレクトリにあるものが読み込まれる。
|
164
|
+
|
165
|
+
~/.automatic/plugins が存在する場合、それらも同様に読み込まれる。
|
166
|
+
|
167
|
+
自作したプラグインはホームディレクトリに置くと良い。
|
168
|
+
(プラグインの作り方は後述する)
|
169
|
+
|
170
|
+
以下のコマンドで自作したプラグインのためのサブディレクトリを自動生成できる。
|
171
|
+
$ automatic-config scaffold
|
172
|
+
(automatic-config の説明は後述する)
|
173
|
+
|
174
|
+
|
60
175
|
==========================
|
61
176
|
ディレクトリとファイル構成
|
62
177
|
==========================
|
@@ -65,7 +180,10 @@ $ automatic -c config/feed2console.yml
|
|
65
180
|
+- bin
|
66
181
|
| |
|
67
182
|
| +- automatic
|
68
|
-
|
|
183
|
+
| | 実行ファイル本体
|
184
|
+
| |
|
185
|
+
| +- automatic-config
|
186
|
+
| 利用を補助するためのツール
|
69
187
|
|
|
70
188
|
+- config
|
71
189
|
| |
|
@@ -80,13 +198,23 @@ $ automatic -c config/feed2console.yml
|
|
80
198
|
|
|
81
199
|
+- plugins
|
82
200
|
| |
|
83
|
-
| +- subscription
|
201
|
+
| +- subscription
|
202
|
+
| | フィードを購読するプラグイン
|
203
|
+
| |
|
204
|
+
| +- customfeed
|
205
|
+
| | カスタムフィードを生成するプラグイン
|
206
|
+
| |
|
207
|
+
| +- filter
|
208
|
+
| | 情報をフィルタリングするプラグイン
|
84
209
|
| |
|
85
|
-
| +-
|
210
|
+
| +- store
|
211
|
+
| | 情報を内部に保存するプラグイン
|
86
212
|
| |
|
87
|
-
| +-
|
213
|
+
| +- notify
|
214
|
+
| | 情報を通知するプラグイン
|
88
215
|
| |
|
89
|
-
| +- publish
|
216
|
+
| +- publish
|
217
|
+
| 外部に情報を送信するプラグイン
|
90
218
|
|
|
91
219
|
| プラグインを開発した場合は plugins ディレクトリに置く
|
92
220
|
|
|
@@ -97,23 +225,24 @@ $ automatic -c config/feed2console.yml
|
|
97
225
|
| | Automatic モジュールの定義をする
|
98
226
|
| |
|
99
227
|
| +- automatic
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
228
|
+
| | |
|
229
|
+
| | +- environment.rb
|
230
|
+
| | | 環境情報を読み込む
|
231
|
+
| | |
|
232
|
+
| | +- pipeline.rb
|
233
|
+
| | | パイプラインと呼ばれるオブジェクトを利用し
|
234
|
+
| | | レシピに書かれたプラグインに処理を順次受け渡す
|
235
|
+
| | |
|
236
|
+
| | +- feed_parser.rb
|
237
|
+
| | | フィードを解析する
|
238
|
+
| | |
|
239
|
+
| | +- log.rb
|
240
|
+
| | | ログを出力する
|
241
|
+
| | |
|
242
|
+
| | +- recipe.rb
|
243
|
+
| | レシピを読み込む
|
244
|
+
| |
|
245
|
+
| +- config
|
117
246
|
|
|
118
247
|
+- db
|
119
248
|
| |
|
@@ -155,85 +284,6 @@ $ automatic -c config/feed2console.yml
|
|
155
284
|
本ドキュメント
|
156
285
|
|
157
286
|
|
158
|
-
==========
|
159
|
-
レシピとは
|
160
|
-
==========
|
161
|
-
|
162
|
-
Automatic Ruby では YAML 形式で書かれた設定ファイル
|
163
|
-
を解析し、呼び出すプラグインの情報とそれに伴う様々な
|
164
|
-
情報を読み込む。
|
165
|
-
|
166
|
-
この YAML ファイルをレシピと呼ぶ。
|
167
|
-
|
168
|
-
automatic.rb 起動時に -c オプションの引数にレシピの
|
169
|
-
ファイル名を指定することができる。オプションが省略
|
170
|
-
された場合は config/default.yml が呼ばれる。
|
171
|
-
|
172
|
-
[起動例]
|
173
|
-
$ automatic -c ~/recipes/your_recipe.yml
|
174
|
-
|
175
|
-
|
176
|
-
================
|
177
|
-
レシピの記述方法
|
178
|
-
================
|
179
|
-
|
180
|
-
レシピには暗黙的な命名規則がある。
|
181
|
-
以下はレシピのサンプルとなる YAML ファイルである。
|
182
|
-
|
183
|
-
global:
|
184
|
-
timezone: Asia/Tokyo
|
185
|
-
cache:
|
186
|
-
base: /tmp
|
187
|
-
log:
|
188
|
-
level: info
|
189
|
-
|
190
|
-
plugins:
|
191
|
-
- module: SubscriptionFeed
|
192
|
-
config:
|
193
|
-
feeds:
|
194
|
-
- http://example.com/rss2
|
195
|
-
- http://hogefuga.com/feed
|
196
|
-
|
197
|
-
- module: FilterIgnore
|
198
|
-
config:
|
199
|
-
link:
|
200
|
-
- hoge
|
201
|
-
- fuga
|
202
|
-
|
203
|
-
- module: StorePermalink
|
204
|
-
config:
|
205
|
-
db: permalink.db
|
206
|
-
|
207
|
-
- module: PublishHatenaBookmark
|
208
|
-
config:
|
209
|
-
username: your_hatena_id
|
210
|
-
password: your_password
|
211
|
-
interval: 5
|
212
|
-
|
213
|
-
このサンプルレシピの例では
|
214
|
-
1. フィードを購読し
|
215
|
-
2. 無視キーワードを含む URL を除外し
|
216
|
-
3. パーマリンクをデータベースに保存し
|
217
|
-
4. はてなブックマークをする
|
218
|
-
という一連の処理をプラグインの組み合わせで実現している。
|
219
|
-
|
220
|
-
このように、プラグインと設定情報をレシピに記述するだけで
|
221
|
-
複数のプラグインの組み合わせにより自由な処理を実現できる。
|
222
|
-
|
223
|
-
たとえば Twitter に投稿する Publish::Twitter プラグインを
|
224
|
-
作成し上記のレシピに記述すれば、自動的に記事を Twitter に
|
225
|
-
投稿する自動処理が完成する。
|
226
|
-
|
227
|
-
Gmail に送信する Publish::Gmail プラグインを作成しレシピ
|
228
|
-
に追加すれば、フィードの記事を Gmail で読めるようになる。
|
229
|
-
|
230
|
-
他にもリンクを元にブログ記事全文を取得したり、電子書籍の
|
231
|
-
形式にして Kindle で読めるようにしたり、データベースに
|
232
|
-
ブログ記事全文を保存してローカルのアプリで読めるように
|
233
|
-
したりなど、プラグインの組み合わせ次第で無限の可能性を
|
234
|
-
実現することができる。
|
235
|
-
|
236
|
-
|
237
287
|
|
238
288
|
==============
|
239
289
|
開発に参加する
|
@@ -245,6 +295,9 @@ https://github.com/id774/automaticruby
|
|
245
295
|
課題
|
246
296
|
https://github.com/id774/automaticruby/issues
|
247
297
|
|
298
|
+
RubyForge
|
299
|
+
http://rubyforge.org/projects/automatic/
|
300
|
+
|
248
301
|
CI
|
249
302
|
http://id774.net/jenkins/
|
250
303
|
|
@@ -350,21 +403,30 @@ end
|
|
350
403
|
戻り値を検査する。
|
351
404
|
|
352
405
|
|
353
|
-
========================
|
354
|
-
継続的インテグレーション
|
355
|
-
========================
|
356
|
-
|
357
|
-
CI は Jenkins でおこなう。
|
358
|
-
http://id774.net/jenkins/
|
359
|
-
|
360
|
-
|
361
406
|
========
|
362
407
|
結合試験
|
363
408
|
========
|
364
409
|
|
365
|
-
test/integration
|
410
|
+
test/integration 配下に複数のプラグインを結合して
|
366
411
|
テストをおこなうためのレシピを置く。
|
367
412
|
|
413
|
+
リポジトリにチェックインする前に必ず ruby 1.9 で
|
414
|
+
以下のコマンドを実施し、テストビルドを実施する。
|
415
|
+
|
416
|
+
$ script/build
|
417
|
+
|
418
|
+
これによりすべての RSpec によるテスト、及び
|
419
|
+
結合試験が実施される。途中で失敗することなくすべて
|
420
|
+
のテストが成功したことを確認した上でリポジトリに
|
421
|
+
チェックインする。さもなくば CI が失敗するだろう。
|
422
|
+
|
423
|
+
|
424
|
+
========================
|
425
|
+
継続的インテグレーション
|
426
|
+
========================
|
427
|
+
|
428
|
+
CI は Jenkins でおこなう。
|
429
|
+
http://id774.net/jenkins/
|
368
430
|
|
369
431
|
|
370
432
|
================
|
@@ -375,34 +437,35 @@ doc/PLUGINS.ja を参照
|
|
375
437
|
|
376
438
|
|
377
439
|
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
utils の下にはフィードを扱うための補助的なツールが
|
383
|
-
置かれる。内容は以下のとおりである。
|
384
|
-
|
440
|
+
================
|
441
|
+
automatic-config
|
442
|
+
================
|
385
443
|
|
386
|
-
|
387
|
-
utils/opml_parser.rb
|
444
|
+
automatic-config は補助的なツールである。
|
388
445
|
|
389
|
-
|
390
|
-
|
391
|
-
|
446
|
+
[書式]
|
447
|
+
$ automatic-config
|
448
|
+
サブコマンドの一覧を表示する
|
392
449
|
|
393
|
-
$
|
450
|
+
$ automatic-config scaffold
|
451
|
+
ホームディレクトリに ~/.automatic ディレクトリを生成する。
|
394
452
|
|
453
|
+
$ automatic-config autodiscovery <url>
|
454
|
+
対象の URL をオートディスカバリで探知しフィードの URL を返す。
|
455
|
+
(例)
|
456
|
+
$ automatic-config autodiscovery http://id774.net/blog
|
457
|
+
["http://id774.net/blog/feed/", "http://id774.net/blog/comments/feed/"]
|
395
458
|
|
396
|
-
|
397
|
-
|
459
|
+
$ automatic-config opmlparser <opml path>
|
460
|
+
OPML ファイルを解析し URL を出力する。
|
461
|
+
(例)
|
462
|
+
$ automatic-config opmlparser opml.xml > feeds.txt
|
398
463
|
|
399
|
-
|
400
|
-
|
401
|
-
オートディスカバリで抽出する。
|
464
|
+
$ automatic-config feedparser <url>
|
465
|
+
フィードを解析して内容を返す。
|
402
466
|
|
403
|
-
$
|
404
|
-
|
405
|
-
"https://twitter.com/favorites/4441691.rss"]
|
467
|
+
$ automatic-config log <level> <message>
|
468
|
+
Automatic Ruby のログ形式でメッセージを出力する。
|
406
469
|
|
407
470
|
|
408
471
|
|
@@ -436,6 +499,7 @@ hashie
|
|
436
499
|
activerecord
|
437
500
|
gcalapi
|
438
501
|
xml-simple
|
502
|
+
feedbag
|
439
503
|
|
440
504
|
詳細は Gemfile を参照
|
441
505
|
|
@@ -1,9 +1,8 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
1
|
# -*- coding: utf-8 -*-
|
3
2
|
# Name:: Automatic::FeedParser
|
4
3
|
# Author:: 774 <http://id774.net>
|
5
4
|
# Created:: Feb 19, 2012
|
6
|
-
# Updated::
|
5
|
+
# Updated:: Mar 11, 2012
|
7
6
|
# Copyright:: 774 Copyright (c) 2012
|
8
7
|
# License:: Licensed under the GNU GENERAL PUBLIC LICENSE, Version 3.0.
|
9
8
|
|
@@ -27,10 +26,3 @@ module Automatic
|
|
27
26
|
end
|
28
27
|
end
|
29
28
|
end
|
30
|
-
|
31
|
-
if __FILE__ == $0
|
32
|
-
url = ARGV.shift || abort("Usage: feed_parser.rb <url>")
|
33
|
-
rss_results = Automatic::FeedParser.get_rss(url)
|
34
|
-
require 'pp'
|
35
|
-
pp links
|
36
|
-
end
|
data/lib/automatic/log.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
1
|
# -*- coding: utf-8 -*-
|
3
2
|
# Name:: Automatic::Log
|
4
3
|
# Author:: 774 <http://id774.net>
|
5
4
|
# Created:: Feb 20, 2012
|
6
|
-
# Updated::
|
5
|
+
# Updated:: Mar 11, 2012
|
7
6
|
# Copyright:: 774 Copyright (c) 2012
|
8
7
|
# License:: Licensed under the GNU GENERAL PUBLIC LICENSE, Version 3.0.
|
9
8
|
|
@@ -15,10 +14,3 @@ module Automatic
|
|
15
14
|
end
|
16
15
|
end
|
17
16
|
end
|
18
|
-
|
19
|
-
if __FILE__ == $0
|
20
|
-
level = ARGV.shift || abort("Usage: log.rb <level> <message>")
|
21
|
-
message = ARGV.shift
|
22
|
-
Automatic::Log.puts(level, message)
|
23
|
-
end
|
24
|
-
|
@@ -0,0 +1,239 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'rexml/parsers/pullparser'
|
3
|
+
|
4
|
+
module Automatic
|
5
|
+
module OPML
|
6
|
+
def self::unnormalize(text)
|
7
|
+
text.gsub(/&(\w+);/) {
|
8
|
+
x = $1
|
9
|
+
case x
|
10
|
+
when 'lt'
|
11
|
+
'<'
|
12
|
+
when 'gt'
|
13
|
+
'>'
|
14
|
+
when 'quot'
|
15
|
+
'"'
|
16
|
+
when 'amp'
|
17
|
+
'&'
|
18
|
+
when 'apos'
|
19
|
+
"'"
|
20
|
+
when /^\#(\d+)$/
|
21
|
+
[$1.to_i].pack("U")
|
22
|
+
when /^\#x([0-9a-f]+)$/i
|
23
|
+
[$1.hex].pack("U")
|
24
|
+
else
|
25
|
+
raise "unnormalize error '#{x}'"
|
26
|
+
end
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
module DOM
|
31
|
+
class Element
|
32
|
+
include Enumerable
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
@attr = {}
|
36
|
+
@parent = nil
|
37
|
+
@children = []
|
38
|
+
end
|
39
|
+
attr_accessor :parent
|
40
|
+
attr_reader :attr, :children
|
41
|
+
|
42
|
+
def each
|
43
|
+
yield self
|
44
|
+
@children.each {|c|
|
45
|
+
c.each {|x|
|
46
|
+
yield x
|
47
|
+
}
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def <<(elem)
|
52
|
+
elem.parent = self if elem.respond_to? :parent=
|
53
|
+
@children << elem
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
def [](n)
|
58
|
+
if n.is_a? String
|
59
|
+
@attr[n]
|
60
|
+
else
|
61
|
+
@children[n]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def []=(k,v)
|
66
|
+
if k.is_a? String
|
67
|
+
@attr[k] = v
|
68
|
+
else
|
69
|
+
@children[k] = v
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def text
|
74
|
+
@attr['text']
|
75
|
+
end
|
76
|
+
|
77
|
+
def type
|
78
|
+
@attr['type']
|
79
|
+
end
|
80
|
+
|
81
|
+
def is_comment?
|
82
|
+
@attr['isComment']=='true'
|
83
|
+
end
|
84
|
+
|
85
|
+
def is_breaakpoint?
|
86
|
+
@attr['isBreakpoint']=='true'
|
87
|
+
end
|
88
|
+
end # Element
|
89
|
+
|
90
|
+
class OPML < Element
|
91
|
+
def head
|
92
|
+
@children.find {|x| x.is_a? Head }
|
93
|
+
end
|
94
|
+
|
95
|
+
def body
|
96
|
+
@children.find {|x| x.is_a? Body }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class Head < Element
|
101
|
+
end
|
102
|
+
|
103
|
+
class Body < Element
|
104
|
+
def outline
|
105
|
+
@children.find {|x| x.is_a? Outline }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class Outline < Element
|
110
|
+
def method_missing(name,*arg,&block)
|
111
|
+
name = name.to_s
|
112
|
+
case name
|
113
|
+
when /^[a-z][0-9a-zA-Z_]*$/
|
114
|
+
key = name.gsub(/_([a-z])/) {|x| ".#{$1.upcase}" }
|
115
|
+
@attr[key]
|
116
|
+
else
|
117
|
+
raise NoMethodError
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end # Model
|
122
|
+
|
123
|
+
class Parser
|
124
|
+
def initialize(port)
|
125
|
+
@p = REXML::Parsers::PullParser.new(port)
|
126
|
+
@opml = nil
|
127
|
+
end
|
128
|
+
|
129
|
+
def parse_tree
|
130
|
+
root = cur = nil
|
131
|
+
while event=@p.pull
|
132
|
+
case event.event_type
|
133
|
+
when :xmldecl
|
134
|
+
encoding = event[1]
|
135
|
+
when :start_element
|
136
|
+
case event[0]
|
137
|
+
when "opml"
|
138
|
+
root = cur = DOM::OPML.new
|
139
|
+
when "head"
|
140
|
+
e = DOM::Head.new
|
141
|
+
cur << e
|
142
|
+
cur = e
|
143
|
+
when "body"
|
144
|
+
e = DOM::Body.new
|
145
|
+
cur << e
|
146
|
+
cur = e
|
147
|
+
when "outline"
|
148
|
+
e = DOM::Outline.new
|
149
|
+
cur << e
|
150
|
+
cur = e
|
151
|
+
else
|
152
|
+
cur['.' + event[0]] = read_text
|
153
|
+
end
|
154
|
+
event[1].each {|k,v|
|
155
|
+
cur[k] = OPML::unnormalize(v)
|
156
|
+
}
|
157
|
+
when :end_element
|
158
|
+
cur = cur.parent
|
159
|
+
when :text
|
160
|
+
when :cdata
|
161
|
+
when :start_doctype
|
162
|
+
when :end_doctype,:comment
|
163
|
+
when :end_document
|
164
|
+
break
|
165
|
+
else
|
166
|
+
p event.event_type
|
167
|
+
p event
|
168
|
+
raise "unknown event"
|
169
|
+
end
|
170
|
+
end # while
|
171
|
+
root
|
172
|
+
end # parse_tree
|
173
|
+
|
174
|
+
def each_outline
|
175
|
+
root = cur = nil
|
176
|
+
while event=@p.pull
|
177
|
+
case event.event_type
|
178
|
+
when :xmldecl
|
179
|
+
encoding = event[1]
|
180
|
+
when :start_element
|
181
|
+
case event[0]
|
182
|
+
when "opml"
|
183
|
+
root = cur = DOM::OPML.new
|
184
|
+
when "head"
|
185
|
+
e = DOM::Head.new
|
186
|
+
cur << e
|
187
|
+
cur = e
|
188
|
+
when "body"
|
189
|
+
e = DOM::Body.new
|
190
|
+
cur << e
|
191
|
+
cur = e
|
192
|
+
when "outline"
|
193
|
+
e = DOM::Outline.new
|
194
|
+
# cur << e
|
195
|
+
cur = e
|
196
|
+
else
|
197
|
+
cur['_' + event[0]] = read_text
|
198
|
+
end
|
199
|
+
event[1].each {|k,v|
|
200
|
+
cur[k] = OPML::unnormalize(v)
|
201
|
+
}
|
202
|
+
if event[0]=="outline"
|
203
|
+
yield root, cur
|
204
|
+
end
|
205
|
+
when :end_element
|
206
|
+
cur = cur.parent if cur.kind_of? DOM::Element
|
207
|
+
when :text
|
208
|
+
when :cdata
|
209
|
+
when :start_doctype
|
210
|
+
when :end_doctype,:comment
|
211
|
+
when :end_document
|
212
|
+
break
|
213
|
+
else
|
214
|
+
p event.event_type
|
215
|
+
p event
|
216
|
+
raise "unknown event"
|
217
|
+
end
|
218
|
+
end # while
|
219
|
+
end # each_outline
|
220
|
+
|
221
|
+
def read_text
|
222
|
+
text = ""
|
223
|
+
while event = @p.pull
|
224
|
+
case event.event_type
|
225
|
+
when :end_element
|
226
|
+
break
|
227
|
+
when :text
|
228
|
+
text << OPML::unnormalize(event[0])
|
229
|
+
when :cdata
|
230
|
+
text << event[0]
|
231
|
+
else
|
232
|
+
raise
|
233
|
+
end
|
234
|
+
end
|
235
|
+
text
|
236
|
+
end
|
237
|
+
end # Parser
|
238
|
+
end # OPML
|
239
|
+
end
|