rubysl-rss 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.travis.yml +8 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE +25 -0
  6. data/README.md +29 -0
  7. data/Rakefile +1 -0
  8. data/lib/rss.rb +1 -0
  9. data/lib/rss/0.9.rb +428 -0
  10. data/lib/rss/1.0.rb +452 -0
  11. data/lib/rss/2.0.rb +111 -0
  12. data/lib/rss/atom.rb +749 -0
  13. data/lib/rss/content.rb +31 -0
  14. data/lib/rss/content/1.0.rb +10 -0
  15. data/lib/rss/content/2.0.rb +12 -0
  16. data/lib/rss/converter.rb +162 -0
  17. data/lib/rss/dublincore.rb +161 -0
  18. data/lib/rss/dublincore/1.0.rb +13 -0
  19. data/lib/rss/dublincore/2.0.rb +13 -0
  20. data/lib/rss/dublincore/atom.rb +17 -0
  21. data/lib/rss/image.rb +193 -0
  22. data/lib/rss/itunes.rb +410 -0
  23. data/lib/rss/maker.rb +44 -0
  24. data/lib/rss/maker/0.9.rb +467 -0
  25. data/lib/rss/maker/1.0.rb +434 -0
  26. data/lib/rss/maker/2.0.rb +223 -0
  27. data/lib/rss/maker/atom.rb +172 -0
  28. data/lib/rss/maker/base.rb +868 -0
  29. data/lib/rss/maker/content.rb +21 -0
  30. data/lib/rss/maker/dublincore.rb +124 -0
  31. data/lib/rss/maker/entry.rb +163 -0
  32. data/lib/rss/maker/feed.rb +429 -0
  33. data/lib/rss/maker/image.rb +111 -0
  34. data/lib/rss/maker/itunes.rb +242 -0
  35. data/lib/rss/maker/slash.rb +33 -0
  36. data/lib/rss/maker/syndication.rb +18 -0
  37. data/lib/rss/maker/taxonomy.rb +118 -0
  38. data/lib/rss/maker/trackback.rb +61 -0
  39. data/lib/rss/parser.rb +541 -0
  40. data/lib/rss/rexmlparser.rb +54 -0
  41. data/lib/rss/rss.rb +1312 -0
  42. data/lib/rss/slash.rb +49 -0
  43. data/lib/rss/syndication.rb +67 -0
  44. data/lib/rss/taxonomy.rb +145 -0
  45. data/lib/rss/trackback.rb +288 -0
  46. data/lib/rss/utils.rb +111 -0
  47. data/lib/rss/xml-stylesheet.rb +105 -0
  48. data/lib/rss/xml.rb +71 -0
  49. data/lib/rss/xmlparser.rb +93 -0
  50. data/lib/rss/xmlscanner.rb +121 -0
  51. data/lib/rubysl/rss.rb +2 -0
  52. data/lib/rubysl/rss/rss.rb +19 -0
  53. data/lib/rubysl/rss/version.rb +5 -0
  54. data/rubysl-rss.gemspec +23 -0
  55. metadata +153 -0
@@ -0,0 +1,61 @@
1
+ require 'rss/trackback'
2
+ require 'rss/maker/1.0'
3
+ require 'rss/maker/2.0'
4
+
5
+ module RSS
6
+ module Maker
7
+ module TrackBackModel
8
+ def self.append_features(klass)
9
+ super
10
+
11
+ klass.def_other_element("#{RSS::TRACKBACK_PREFIX}_ping")
12
+ klass.def_classed_elements("#{RSS::TRACKBACK_PREFIX}_about", "value",
13
+ "TrackBackAbouts")
14
+ end
15
+
16
+ class TrackBackAboutsBase < Base
17
+ def_array_element("about", nil, "TrackBackAbout")
18
+
19
+ class TrackBackAboutBase < Base
20
+ attr_accessor :value
21
+ add_need_initialize_variable("value")
22
+
23
+ alias_method(:resource, :value)
24
+ alias_method(:resource=, :value=)
25
+ alias_method(:content, :value)
26
+ alias_method(:content=, :value=)
27
+
28
+ def have_required_values?
29
+ @value
30
+ end
31
+
32
+ def to_feed(feed, current)
33
+ if current.respond_to?(:trackback_abouts) and have_required_values?
34
+ about = current.class::TrackBackAbout.new
35
+ setup_values(about)
36
+ setup_other_elements(about)
37
+ current.trackback_abouts << about
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ class ItemsBase
45
+ class ItemBase; include TrackBackModel; end
46
+ end
47
+
48
+ makers.each do |maker|
49
+ maker.module_eval(<<-EOC, __FILE__, __LINE__ + 1)
50
+ class Items
51
+ class Item
52
+ class TrackBackAbouts < TrackBackAboutsBase
53
+ class TrackBackAbout < TrackBackAboutBase
54
+ end
55
+ end
56
+ end
57
+ end
58
+ EOC
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,541 @@
1
+ require "forwardable"
2
+ require "open-uri"
3
+
4
+ require "rss/rss"
5
+ require "rss/xml"
6
+
7
+ module RSS
8
+
9
+ class NotWellFormedError < Error
10
+ attr_reader :line, :element
11
+
12
+ # Create a new NotWellFormedError for an error at +line+
13
+ # in +element+. If a block is given the return value of
14
+ # the block ends up in the error message.
15
+ def initialize(line=nil, element=nil)
16
+ message = "This is not well formed XML"
17
+ if element or line
18
+ message << "\nerror occurred"
19
+ message << " in #{element}" if element
20
+ message << " at about #{line} line" if line
21
+ end
22
+ message << "\n#{yield}" if block_given?
23
+ super(message)
24
+ end
25
+ end
26
+
27
+ class XMLParserNotFound < Error
28
+ def initialize
29
+ super("available XML parser was not found in " <<
30
+ "#{AVAILABLE_PARSER_LIBRARIES.inspect}.")
31
+ end
32
+ end
33
+
34
+ class NotValidXMLParser < Error
35
+ def initialize(parser)
36
+ super("#{parser} is not an available XML parser. " <<
37
+ "Available XML parser"<<
38
+ (AVAILABLE_PARSERS.size > 1 ? "s are ": " is ") <<
39
+ "#{AVAILABLE_PARSERS.inspect}.")
40
+ end
41
+ end
42
+
43
+ class NSError < InvalidRSSError
44
+ attr_reader :tag, :prefix, :uri
45
+ def initialize(tag, prefix, require_uri)
46
+ @tag, @prefix, @uri = tag, prefix, require_uri
47
+ super("prefix <#{prefix}> doesn't associate uri " <<
48
+ "<#{require_uri}> in tag <#{tag}>")
49
+ end
50
+ end
51
+
52
+ class Parser
53
+
54
+ extend Forwardable
55
+
56
+ class << self
57
+
58
+ @@default_parser = nil
59
+
60
+ def default_parser
61
+ @@default_parser || AVAILABLE_PARSERS.first
62
+ end
63
+
64
+ # Set @@default_parser to new_value if it is one of the
65
+ # available parsers. Else raise NotValidXMLParser error.
66
+ def default_parser=(new_value)
67
+ if AVAILABLE_PARSERS.include?(new_value)
68
+ @@default_parser = new_value
69
+ else
70
+ raise NotValidXMLParser.new(new_value)
71
+ end
72
+ end
73
+
74
+ def parse(rss, do_validate=true, ignore_unknown_element=true,
75
+ parser_class=default_parser)
76
+ parser = new(rss, parser_class)
77
+ parser.do_validate = do_validate
78
+ parser.ignore_unknown_element = ignore_unknown_element
79
+ parser.parse
80
+ end
81
+ end
82
+
83
+ def_delegators(:@parser, :parse, :rss,
84
+ :ignore_unknown_element,
85
+ :ignore_unknown_element=, :do_validate,
86
+ :do_validate=)
87
+
88
+ def initialize(rss, parser_class=self.class.default_parser)
89
+ @parser = parser_class.new(normalize_rss(rss))
90
+ end
91
+
92
+ private
93
+
94
+ # Try to get the XML associated with +rss+.
95
+ # Return +rss+ if it already looks like XML, or treat it as a URI,
96
+ # or a file to get the XML,
97
+ def normalize_rss(rss)
98
+ return rss if maybe_xml?(rss)
99
+
100
+ uri = to_uri(rss)
101
+
102
+ if uri.respond_to?(:read)
103
+ uri.read
104
+ elsif !rss.tainted? and File.readable?(rss)
105
+ File.open(rss) {|f| f.read}
106
+ else
107
+ rss
108
+ end
109
+ end
110
+
111
+ # maybe_xml? tests if source is a string that looks like XML.
112
+ def maybe_xml?(source)
113
+ source.is_a?(String) and /</ =~ source
114
+ end
115
+
116
+ # Attempt to convert rss to a URI, but just return it if
117
+ # there's a ::URI::Error
118
+ def to_uri(rss)
119
+ return rss if rss.is_a?(::URI::Generic)
120
+
121
+ begin
122
+ ::URI.parse(rss)
123
+ rescue ::URI::Error
124
+ rss
125
+ end
126
+ end
127
+ end
128
+
129
+ class BaseParser
130
+
131
+ class << self
132
+ def raise_for_undefined_entity?
133
+ listener.raise_for_undefined_entity?
134
+ end
135
+ end
136
+
137
+ def initialize(rss)
138
+ @listener = self.class.listener.new
139
+ @rss = rss
140
+ end
141
+
142
+ def rss
143
+ @listener.rss
144
+ end
145
+
146
+ def ignore_unknown_element
147
+ @listener.ignore_unknown_element
148
+ end
149
+
150
+ def ignore_unknown_element=(new_value)
151
+ @listener.ignore_unknown_element = new_value
152
+ end
153
+
154
+ def do_validate
155
+ @listener.do_validate
156
+ end
157
+
158
+ def do_validate=(new_value)
159
+ @listener.do_validate = new_value
160
+ end
161
+
162
+ def parse
163
+ if @listener.rss.nil?
164
+ _parse
165
+ end
166
+ @listener.rss
167
+ end
168
+
169
+ end
170
+
171
+ class BaseListener
172
+
173
+ extend Utils
174
+
175
+ class << self
176
+
177
+ @@accessor_bases = {}
178
+ @@registered_uris = {}
179
+ @@class_names = {}
180
+
181
+ # return the setter for the uri, tag_name pair, or nil.
182
+ def setter(uri, tag_name)
183
+ _getter = getter(uri, tag_name)
184
+ if _getter
185
+ "#{_getter}="
186
+ else
187
+ nil
188
+ end
189
+ end
190
+
191
+ def getter(uri, tag_name)
192
+ (@@accessor_bases[uri] || {})[tag_name]
193
+ end
194
+
195
+ # return the tag_names for setters associated with uri
196
+ def available_tags(uri)
197
+ (@@accessor_bases[uri] || {}).keys
198
+ end
199
+
200
+ # register uri against this name.
201
+ def register_uri(uri, name)
202
+ @@registered_uris[name] ||= {}
203
+ @@registered_uris[name][uri] = nil
204
+ end
205
+
206
+ # test if this uri is registered against this name
207
+ def uri_registered?(uri, name)
208
+ @@registered_uris[name].has_key?(uri)
209
+ end
210
+
211
+ # record class_name for the supplied uri and tag_name
212
+ def install_class_name(uri, tag_name, class_name)
213
+ @@class_names[uri] ||= {}
214
+ @@class_names[uri][tag_name] = class_name
215
+ end
216
+
217
+ # retrieve class_name for the supplied uri and tag_name
218
+ # If it doesn't exist, capitalize the tag_name
219
+ def class_name(uri, tag_name)
220
+ name = (@@class_names[uri] || {})[tag_name]
221
+ return name if name
222
+
223
+ tag_name = tag_name.gsub(/[_\-]([a-z]?)/) do
224
+ $1.upcase
225
+ end
226
+ tag_name[0, 1].upcase + tag_name[1..-1]
227
+ end
228
+
229
+ def install_get_text_element(uri, name, accessor_base)
230
+ install_accessor_base(uri, name, accessor_base)
231
+ def_get_text_element(uri, name, *get_file_and_line_from_caller(1))
232
+ end
233
+
234
+ def raise_for_undefined_entity?
235
+ true
236
+ end
237
+
238
+ private
239
+ # set the accessor for the uri, tag_name pair
240
+ def install_accessor_base(uri, tag_name, accessor_base)
241
+ @@accessor_bases[uri] ||= {}
242
+ @@accessor_bases[uri][tag_name] = accessor_base.chomp("=")
243
+ end
244
+
245
+ def def_get_text_element(uri, element_name, file, line)
246
+ register_uri(uri, element_name)
247
+ method_name = "start_#{element_name}"
248
+ unless private_method_defined?(method_name)
249
+ define_method(method_name) do |name, prefix, attrs, ns|
250
+ uri = _ns(ns, prefix)
251
+ if self.class.uri_registered?(uri, element_name)
252
+ start_get_text_element(name, prefix, ns, uri)
253
+ else
254
+ start_else_element(name, prefix, attrs, ns)
255
+ end
256
+ end
257
+ private(method_name)
258
+ end
259
+ end
260
+ end
261
+ end
262
+
263
+ module ListenerMixin
264
+ attr_reader :rss
265
+
266
+ attr_accessor :ignore_unknown_element
267
+ attr_accessor :do_validate
268
+
269
+ def initialize
270
+ @rss = nil
271
+ @ignore_unknown_element = true
272
+ @do_validate = true
273
+ @ns_stack = [{"xml" => :xml}]
274
+ @tag_stack = [[]]
275
+ @text_stack = ['']
276
+ @proc_stack = []
277
+ @last_element = nil
278
+ @version = @encoding = @standalone = nil
279
+ @xml_stylesheets = []
280
+ @xml_child_mode = false
281
+ @xml_element = nil
282
+ @last_xml_element = nil
283
+ end
284
+
285
+ # set instance vars for version, encoding, standalone
286
+ def xmldecl(version, encoding, standalone)
287
+ @version, @encoding, @standalone = version, encoding, standalone
288
+ end
289
+
290
+ def instruction(name, content)
291
+ if name == "xml-stylesheet"
292
+ params = parse_pi_content(content)
293
+ if params.has_key?("href")
294
+ @xml_stylesheets << XMLStyleSheet.new(params)
295
+ end
296
+ end
297
+ end
298
+
299
+ def tag_start(name, attributes)
300
+ @text_stack.push('')
301
+
302
+ ns = @ns_stack.last.dup
303
+ attrs = {}
304
+ attributes.each do |n, v|
305
+ if /\Axmlns(?:\z|:)/ =~ n
306
+ ns[$'] = v
307
+ else
308
+ attrs[n] = v
309
+ end
310
+ end
311
+ @ns_stack.push(ns)
312
+
313
+ prefix, local = split_name(name)
314
+ @tag_stack.last.push([_ns(ns, prefix), local])
315
+ @tag_stack.push([])
316
+ if @xml_child_mode
317
+ previous = @last_xml_element
318
+ element_attrs = attributes.dup
319
+ unless previous
320
+ ns.each do |ns_prefix, value|
321
+ next if ns_prefix == "xml"
322
+ key = ns_prefix.empty? ? "xmlns" : "xmlns:#{ns_prefix}"
323
+ element_attrs[key] ||= value
324
+ end
325
+ end
326
+ next_element = XML::Element.new(local,
327
+ prefix.empty? ? nil : prefix,
328
+ _ns(ns, prefix),
329
+ element_attrs)
330
+ previous << next_element if previous
331
+ @last_xml_element = next_element
332
+ pr = Proc.new do |text, tags|
333
+ if previous
334
+ @last_xml_element = previous
335
+ else
336
+ @xml_element = @last_xml_element
337
+ @last_xml_element = nil
338
+ end
339
+ end
340
+ @proc_stack.push(pr)
341
+ else
342
+ if @rss.nil? and respond_to?("initial_start_#{local}", true)
343
+ __send__("initial_start_#{local}", local, prefix, attrs, ns.dup)
344
+ elsif respond_to?("start_#{local}", true)
345
+ __send__("start_#{local}", local, prefix, attrs, ns.dup)
346
+ else
347
+ start_else_element(local, prefix, attrs, ns.dup)
348
+ end
349
+ end
350
+ end
351
+
352
+ def tag_end(name)
353
+ if DEBUG
354
+ p "end tag #{name}"
355
+ p @tag_stack
356
+ end
357
+ text = @text_stack.pop
358
+ tags = @tag_stack.pop
359
+ pr = @proc_stack.pop
360
+ pr.call(text, tags) unless pr.nil?
361
+ @ns_stack.pop
362
+ end
363
+
364
+ def text(data)
365
+ if @xml_child_mode
366
+ @last_xml_element << data if @last_xml_element
367
+ else
368
+ @text_stack.last << data
369
+ end
370
+ end
371
+
372
+ private
373
+ def _ns(ns, prefix)
374
+ ns.fetch(prefix, "")
375
+ end
376
+
377
+ CONTENT_PATTERN = /\s*([^=]+)=(["'])([^\2]+?)\2/
378
+ # Extract the first name="value" pair from content.
379
+ # Works with single quotes according to the constant
380
+ # CONTENT_PATTERN. Return a Hash.
381
+ def parse_pi_content(content)
382
+ params = {}
383
+ content.scan(CONTENT_PATTERN) do |name, quote, value|
384
+ params[name] = value
385
+ end
386
+ params
387
+ end
388
+
389
+ def start_else_element(local, prefix, attrs, ns)
390
+ class_name = self.class.class_name(_ns(ns, prefix), local)
391
+ current_class = @last_element.class
392
+ if class_name and
393
+ (current_class.const_defined?(class_name) or
394
+ current_class.constants.include?(class_name))
395
+ next_class = current_class.const_get(class_name)
396
+ start_have_something_element(local, prefix, attrs, ns, next_class)
397
+ else
398
+ if !@do_validate or @ignore_unknown_element
399
+ @proc_stack.push(nil)
400
+ else
401
+ parent = "ROOT ELEMENT???"
402
+ if current_class.tag_name
403
+ parent = current_class.tag_name
404
+ end
405
+ raise NotExpectedTagError.new(local, _ns(ns, prefix), parent)
406
+ end
407
+ end
408
+ end
409
+
410
+ NAMESPLIT = /^(?:([\w:][-\w\d.]*):)?([\w:][-\w\d.]*)/
411
+ def split_name(name)
412
+ name =~ NAMESPLIT
413
+ [$1 || '', $2]
414
+ end
415
+
416
+ def check_ns(tag_name, prefix, ns, require_uri)
417
+ unless _ns(ns, prefix) == require_uri
418
+ if @do_validate
419
+ raise NSError.new(tag_name, prefix, require_uri)
420
+ else
421
+ # Force bind required URI with prefix
422
+ @ns_stack.last[prefix] = require_uri
423
+ end
424
+ end
425
+ end
426
+
427
+ def start_get_text_element(tag_name, prefix, ns, required_uri)
428
+ pr = Proc.new do |text, tags|
429
+ setter = self.class.setter(required_uri, tag_name)
430
+ if @last_element.respond_to?(setter)
431
+ if @do_validate
432
+ getter = self.class.getter(required_uri, tag_name)
433
+ if @last_element.__send__(getter)
434
+ raise TooMuchTagError.new(tag_name, @last_element.tag_name)
435
+ end
436
+ end
437
+ @last_element.__send__(setter, text.to_s)
438
+ else
439
+ if @do_validate and !@ignore_unknown_element
440
+ raise NotExpectedTagError.new(tag_name, _ns(ns, prefix),
441
+ @last_element.tag_name)
442
+ end
443
+ end
444
+ end
445
+ @proc_stack.push(pr)
446
+ end
447
+
448
+ def start_have_something_element(tag_name, prefix, attrs, ns, klass)
449
+ check_ns(tag_name, prefix, ns, klass.required_uri)
450
+ attributes = collect_attributes(tag_name, prefix, attrs, ns, klass)
451
+ @proc_stack.push(setup_next_element(tag_name, klass, attributes))
452
+ end
453
+
454
+ def collect_attributes(tag_name, prefix, attrs, ns, klass)
455
+ attributes = {}
456
+ klass.get_attributes.each do |a_name, a_uri, required, element_name|
457
+ if a_uri.is_a?(String) or !a_uri.respond_to?(:include?)
458
+ a_uri = [a_uri]
459
+ end
460
+ unless a_uri == [""]
461
+ for prefix, uri in ns
462
+ if a_uri.include?(uri)
463
+ val = attrs["#{prefix}:#{a_name}"]
464
+ break if val
465
+ end
466
+ end
467
+ end
468
+ if val.nil? and a_uri.include?("")
469
+ val = attrs[a_name]
470
+ end
471
+
472
+ if @do_validate and required and val.nil?
473
+ unless a_uri.include?("")
474
+ for prefix, uri in ns
475
+ if a_uri.include?(uri)
476
+ a_name = "#{prefix}:#{a_name}"
477
+ end
478
+ end
479
+ end
480
+ raise MissingAttributeError.new(tag_name, a_name)
481
+ end
482
+
483
+ attributes[a_name] = val
484
+ end
485
+ attributes
486
+ end
487
+
488
+ def setup_next_element(tag_name, klass, attributes)
489
+ previous = @last_element
490
+ next_element = klass.new(@do_validate, attributes)
491
+ previous.set_next_element(tag_name, next_element)
492
+ @last_element = next_element
493
+ @last_element.parent = previous if klass.need_parent?
494
+ @xml_child_mode = @last_element.have_xml_content?
495
+
496
+ Proc.new do |text, tags|
497
+ p(@last_element.class) if DEBUG
498
+ if @xml_child_mode
499
+ @last_element.content = @xml_element.to_s
500
+ xml_setter = @last_element.class.xml_setter
501
+ @last_element.__send__(xml_setter, @xml_element)
502
+ @xml_element = nil
503
+ @xml_child_mode = false
504
+ else
505
+ if klass.have_content?
506
+ if @last_element.need_base64_encode?
507
+ text = Base64.decode64(text.lstrip)
508
+ end
509
+ @last_element.content = text
510
+ end
511
+ end
512
+ if @do_validate
513
+ @last_element.validate_for_stream(tags, @ignore_unknown_element)
514
+ end
515
+ @last_element = previous
516
+ end
517
+ end
518
+ end
519
+
520
+ unless const_defined? :AVAILABLE_PARSER_LIBRARIES
521
+ AVAILABLE_PARSER_LIBRARIES = [
522
+ ["rss/xmlparser", :XMLParserParser],
523
+ ["rss/xmlscanner", :XMLScanParser],
524
+ ["rss/rexmlparser", :REXMLParser],
525
+ ]
526
+ end
527
+
528
+ AVAILABLE_PARSERS = []
529
+
530
+ AVAILABLE_PARSER_LIBRARIES.each do |lib, parser|
531
+ begin
532
+ require lib
533
+ AVAILABLE_PARSERS.push(const_get(parser))
534
+ rescue LoadError
535
+ end
536
+ end
537
+
538
+ if AVAILABLE_PARSERS.empty?
539
+ raise XMLParserNotFound
540
+ end
541
+ end