rubysl-rss 1.0.0

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.
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