rss 0.2.7
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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +6 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +88 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/rss.rb +92 -0
- data/lib/rss/0.9.rb +462 -0
- data/lib/rss/1.0.rb +485 -0
- data/lib/rss/2.0.rb +143 -0
- data/lib/rss/atom.rb +1025 -0
- data/lib/rss/content.rb +34 -0
- data/lib/rss/content/1.0.rb +10 -0
- data/lib/rss/content/2.0.rb +12 -0
- data/lib/rss/converter.rb +171 -0
- data/lib/rss/dublincore.rb +164 -0
- data/lib/rss/dublincore/1.0.rb +13 -0
- data/lib/rss/dublincore/2.0.rb +13 -0
- data/lib/rss/dublincore/atom.rb +17 -0
- data/lib/rss/image.rb +198 -0
- data/lib/rss/itunes.rb +413 -0
- data/lib/rss/maker.rb +79 -0
- data/lib/rss/maker/0.9.rb +509 -0
- data/lib/rss/maker/1.0.rb +436 -0
- data/lib/rss/maker/2.0.rb +224 -0
- data/lib/rss/maker/atom.rb +173 -0
- data/lib/rss/maker/base.rb +945 -0
- data/lib/rss/maker/content.rb +22 -0
- data/lib/rss/maker/dublincore.rb +122 -0
- data/lib/rss/maker/entry.rb +164 -0
- data/lib/rss/maker/feed.rb +427 -0
- data/lib/rss/maker/image.rb +112 -0
- data/lib/rss/maker/itunes.rb +243 -0
- data/lib/rss/maker/slash.rb +34 -0
- data/lib/rss/maker/syndication.rb +19 -0
- data/lib/rss/maker/taxonomy.rb +119 -0
- data/lib/rss/maker/trackback.rb +62 -0
- data/lib/rss/parser.rb +589 -0
- data/lib/rss/rexmlparser.rb +50 -0
- data/lib/rss/rss.rb +1346 -0
- data/lib/rss/slash.rb +52 -0
- data/lib/rss/syndication.rb +69 -0
- data/lib/rss/taxonomy.rb +148 -0
- data/lib/rss/trackback.rb +291 -0
- data/lib/rss/utils.rb +200 -0
- data/lib/rss/xml-stylesheet.rb +106 -0
- data/lib/rss/xml.rb +72 -0
- data/lib/rss/xmlparser.rb +95 -0
- data/lib/rss/xmlscanner.rb +122 -0
- data/rss.gemspec +38 -0
- metadata +138 -0
data/lib/rss/itunes.rb
ADDED
@@ -0,0 +1,413 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
require 'rss/2.0'
|
3
|
+
|
4
|
+
module RSS
|
5
|
+
# The prefix for the iTunes XML namespace.
|
6
|
+
ITUNES_PREFIX = 'itunes'
|
7
|
+
# The URI of the iTunes specification.
|
8
|
+
ITUNES_URI = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
|
9
|
+
|
10
|
+
Rss.install_ns(ITUNES_PREFIX, ITUNES_URI)
|
11
|
+
|
12
|
+
module ITunesModelUtils
|
13
|
+
include Utils
|
14
|
+
|
15
|
+
def def_class_accessor(klass, name, type, *args)
|
16
|
+
normalized_name = name.gsub(/-/, "_")
|
17
|
+
full_name = "#{ITUNES_PREFIX}_#{normalized_name}"
|
18
|
+
klass_name = "ITunes#{Utils.to_class_name(normalized_name)}"
|
19
|
+
|
20
|
+
case type
|
21
|
+
when :element, :attribute
|
22
|
+
klass::ELEMENTS << full_name
|
23
|
+
def_element_class_accessor(klass, name, full_name, klass_name, *args)
|
24
|
+
when :elements
|
25
|
+
klass::ELEMENTS << full_name
|
26
|
+
def_elements_class_accessor(klass, name, full_name, klass_name, *args)
|
27
|
+
else
|
28
|
+
klass.install_must_call_validator(ITUNES_PREFIX, ITUNES_URI)
|
29
|
+
klass.install_text_element(normalized_name, ITUNES_URI, "?",
|
30
|
+
full_name, type, name)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def def_element_class_accessor(klass, name, full_name, klass_name,
|
35
|
+
recommended_attribute_name=nil)
|
36
|
+
klass.install_have_child_element(name, ITUNES_PREFIX, "?", full_name)
|
37
|
+
end
|
38
|
+
|
39
|
+
def def_elements_class_accessor(klass, name, full_name, klass_name,
|
40
|
+
plural_name, recommended_attribute_name=nil)
|
41
|
+
full_plural_name = "#{ITUNES_PREFIX}_#{plural_name}"
|
42
|
+
klass.install_have_children_element(name, ITUNES_PREFIX, "*",
|
43
|
+
full_name, full_plural_name)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module ITunesBaseModel
|
48
|
+
extend ITunesModelUtils
|
49
|
+
|
50
|
+
ELEMENTS = []
|
51
|
+
|
52
|
+
ELEMENT_INFOS = [["author"],
|
53
|
+
["block", :yes_other],
|
54
|
+
["explicit", :explicit_clean_other],
|
55
|
+
["keywords", :csv],
|
56
|
+
["subtitle"],
|
57
|
+
["summary"]]
|
58
|
+
end
|
59
|
+
|
60
|
+
module ITunesChannelModel
|
61
|
+
extend BaseModel
|
62
|
+
extend ITunesModelUtils
|
63
|
+
include ITunesBaseModel
|
64
|
+
|
65
|
+
ELEMENTS = []
|
66
|
+
|
67
|
+
class << self
|
68
|
+
def append_features(klass)
|
69
|
+
super
|
70
|
+
|
71
|
+
return if klass.instance_of?(Module)
|
72
|
+
ELEMENT_INFOS.each do |name, type, *additional_infos|
|
73
|
+
def_class_accessor(klass, name, type, *additional_infos)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
ELEMENT_INFOS = [
|
79
|
+
["category", :elements, "categories", "text"],
|
80
|
+
["image", :attribute, "href"],
|
81
|
+
["owner", :element],
|
82
|
+
["new-feed-url"],
|
83
|
+
] + ITunesBaseModel::ELEMENT_INFOS
|
84
|
+
|
85
|
+
class ITunesCategory < Element
|
86
|
+
include RSS09
|
87
|
+
|
88
|
+
@tag_name = "category"
|
89
|
+
|
90
|
+
class << self
|
91
|
+
def required_prefix
|
92
|
+
ITUNES_PREFIX
|
93
|
+
end
|
94
|
+
|
95
|
+
def required_uri
|
96
|
+
ITUNES_URI
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
[
|
101
|
+
["text", "", true]
|
102
|
+
].each do |name, uri, required|
|
103
|
+
install_get_attribute(name, uri, required)
|
104
|
+
end
|
105
|
+
|
106
|
+
ITunesCategory = self
|
107
|
+
install_have_children_element("category", ITUNES_URI, "*",
|
108
|
+
"#{ITUNES_PREFIX}_category",
|
109
|
+
"#{ITUNES_PREFIX}_categories")
|
110
|
+
|
111
|
+
def initialize(*args)
|
112
|
+
if Utils.element_initialize_arguments?(args)
|
113
|
+
super
|
114
|
+
else
|
115
|
+
super()
|
116
|
+
self.text = args[0]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def full_name
|
121
|
+
tag_name_with_prefix(ITUNES_PREFIX)
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
def maker_target(categories)
|
126
|
+
if text or !itunes_categories.empty?
|
127
|
+
categories.new_category
|
128
|
+
else
|
129
|
+
nil
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def setup_maker_attributes(category)
|
134
|
+
category.text = text if text
|
135
|
+
end
|
136
|
+
|
137
|
+
def setup_maker_elements(category)
|
138
|
+
super(category)
|
139
|
+
itunes_categories.each do |sub_category|
|
140
|
+
sub_category.setup_maker(category)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
class ITunesImage < Element
|
146
|
+
include RSS09
|
147
|
+
|
148
|
+
@tag_name = "image"
|
149
|
+
|
150
|
+
class << self
|
151
|
+
def required_prefix
|
152
|
+
ITUNES_PREFIX
|
153
|
+
end
|
154
|
+
|
155
|
+
def required_uri
|
156
|
+
ITUNES_URI
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
[
|
161
|
+
["href", "", true]
|
162
|
+
].each do |name, uri, required|
|
163
|
+
install_get_attribute(name, uri, required)
|
164
|
+
end
|
165
|
+
|
166
|
+
def initialize(*args)
|
167
|
+
if Utils.element_initialize_arguments?(args)
|
168
|
+
super
|
169
|
+
else
|
170
|
+
super()
|
171
|
+
self.href = args[0]
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def full_name
|
176
|
+
tag_name_with_prefix(ITUNES_PREFIX)
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
def maker_target(target)
|
181
|
+
if href
|
182
|
+
target.itunes_image {|image| image}
|
183
|
+
else
|
184
|
+
nil
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def setup_maker_attributes(image)
|
189
|
+
image.href = href
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
class ITunesOwner < Element
|
194
|
+
include RSS09
|
195
|
+
|
196
|
+
@tag_name = "owner"
|
197
|
+
|
198
|
+
class << self
|
199
|
+
def required_prefix
|
200
|
+
ITUNES_PREFIX
|
201
|
+
end
|
202
|
+
|
203
|
+
def required_uri
|
204
|
+
ITUNES_URI
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
install_must_call_validator(ITUNES_PREFIX, ITUNES_URI)
|
209
|
+
[
|
210
|
+
["name"],
|
211
|
+
["email"],
|
212
|
+
].each do |name,|
|
213
|
+
ITunesBaseModel::ELEMENT_INFOS << name
|
214
|
+
install_text_element(name, ITUNES_URI, nil, "#{ITUNES_PREFIX}_#{name}")
|
215
|
+
end
|
216
|
+
|
217
|
+
def initialize(*args)
|
218
|
+
if Utils.element_initialize_arguments?(args)
|
219
|
+
super
|
220
|
+
else
|
221
|
+
super()
|
222
|
+
self.itunes_name = args[0]
|
223
|
+
self.itunes_email = args[1]
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def full_name
|
228
|
+
tag_name_with_prefix(ITUNES_PREFIX)
|
229
|
+
end
|
230
|
+
|
231
|
+
private
|
232
|
+
def maker_target(target)
|
233
|
+
target.itunes_owner
|
234
|
+
end
|
235
|
+
|
236
|
+
def setup_maker_element(owner)
|
237
|
+
super(owner)
|
238
|
+
owner.itunes_name = itunes_name
|
239
|
+
owner.itunes_email = itunes_email
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
module ITunesItemModel
|
245
|
+
extend BaseModel
|
246
|
+
extend ITunesModelUtils
|
247
|
+
include ITunesBaseModel
|
248
|
+
|
249
|
+
class << self
|
250
|
+
def append_features(klass)
|
251
|
+
super
|
252
|
+
|
253
|
+
return if klass.instance_of?(Module)
|
254
|
+
ELEMENT_INFOS.each do |name, type|
|
255
|
+
def_class_accessor(klass, name, type)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
ELEMENT_INFOS = ITunesBaseModel::ELEMENT_INFOS +
|
261
|
+
[["duration", :element, "content"]]
|
262
|
+
|
263
|
+
class ITunesDuration < Element
|
264
|
+
include RSS09
|
265
|
+
|
266
|
+
@tag_name = "duration"
|
267
|
+
|
268
|
+
class << self
|
269
|
+
def required_prefix
|
270
|
+
ITUNES_PREFIX
|
271
|
+
end
|
272
|
+
|
273
|
+
def required_uri
|
274
|
+
ITUNES_URI
|
275
|
+
end
|
276
|
+
|
277
|
+
def parse(duration, do_validate=true)
|
278
|
+
if do_validate and /\A(?:
|
279
|
+
\d?\d:[0-5]\d:[0-5]\d|
|
280
|
+
[0-5]?\d:[0-5]\d
|
281
|
+
)\z/x !~ duration
|
282
|
+
raise ArgumentError,
|
283
|
+
"must be one of HH:MM:SS, H:MM:SS, MM::SS, M:SS: " +
|
284
|
+
duration.inspect
|
285
|
+
end
|
286
|
+
|
287
|
+
components = duration.split(':')
|
288
|
+
components[3..-1] = nil if components.size > 3
|
289
|
+
|
290
|
+
components.unshift("00") until components.size == 3
|
291
|
+
|
292
|
+
components.collect do |component|
|
293
|
+
component.to_i
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def construct(hour, minute, second)
|
298
|
+
components = [minute, second]
|
299
|
+
if components.include?(nil)
|
300
|
+
nil
|
301
|
+
else
|
302
|
+
components.unshift(hour) if hour and hour > 0
|
303
|
+
components.collect do |component|
|
304
|
+
"%02d" % component
|
305
|
+
end.join(":")
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
content_setup
|
311
|
+
alias_method(:value, :content)
|
312
|
+
remove_method(:content=)
|
313
|
+
|
314
|
+
attr_reader :hour, :minute, :second
|
315
|
+
def initialize(*args)
|
316
|
+
if Utils.element_initialize_arguments?(args)
|
317
|
+
super
|
318
|
+
else
|
319
|
+
super()
|
320
|
+
args = args[0] if args.size == 1 and args[0].is_a?(Array)
|
321
|
+
if args.size == 1
|
322
|
+
self.content = args[0]
|
323
|
+
elsif args.size > 3
|
324
|
+
raise ArgumentError,
|
325
|
+
"must be (do_validate, params), (content), " +
|
326
|
+
"(minute, second), ([minute, second]), " +
|
327
|
+
"(hour, minute, second) or ([hour, minute, second]): " +
|
328
|
+
args.inspect
|
329
|
+
else
|
330
|
+
@second, @minute, @hour = args.reverse
|
331
|
+
update_content
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
def content=(value)
|
337
|
+
if value.nil?
|
338
|
+
@content = nil
|
339
|
+
elsif value.is_a?(self.class)
|
340
|
+
self.content = value.content
|
341
|
+
else
|
342
|
+
begin
|
343
|
+
@hour, @minute, @second = self.class.parse(value, @do_validate)
|
344
|
+
rescue ArgumentError
|
345
|
+
raise NotAvailableValueError.new(tag_name, value)
|
346
|
+
end
|
347
|
+
@content = value
|
348
|
+
end
|
349
|
+
end
|
350
|
+
alias_method(:value=, :content=)
|
351
|
+
|
352
|
+
def hour=(hour)
|
353
|
+
@hour = @do_validate ? Integer(hour) : hour.to_i
|
354
|
+
update_content
|
355
|
+
hour
|
356
|
+
end
|
357
|
+
|
358
|
+
def minute=(minute)
|
359
|
+
@minute = @do_validate ? Integer(minute) : minute.to_i
|
360
|
+
update_content
|
361
|
+
minute
|
362
|
+
end
|
363
|
+
|
364
|
+
def second=(second)
|
365
|
+
@second = @do_validate ? Integer(second) : second.to_i
|
366
|
+
update_content
|
367
|
+
second
|
368
|
+
end
|
369
|
+
|
370
|
+
def full_name
|
371
|
+
tag_name_with_prefix(ITUNES_PREFIX)
|
372
|
+
end
|
373
|
+
|
374
|
+
private
|
375
|
+
def update_content
|
376
|
+
@content = self.class.construct(hour, minute, second)
|
377
|
+
end
|
378
|
+
|
379
|
+
def maker_target(target)
|
380
|
+
if @content
|
381
|
+
target.itunes_duration {|duration| duration}
|
382
|
+
else
|
383
|
+
nil
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
def setup_maker_element(duration)
|
388
|
+
super(duration)
|
389
|
+
duration.content = @content
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
class Rss
|
395
|
+
class Channel
|
396
|
+
include ITunesChannelModel
|
397
|
+
class Item; include ITunesItemModel; end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
element_infos =
|
402
|
+
ITunesChannelModel::ELEMENT_INFOS + ITunesItemModel::ELEMENT_INFOS
|
403
|
+
element_infos.each do |name, type|
|
404
|
+
case type
|
405
|
+
when :element, :elements, :attribute
|
406
|
+
class_name = Utils.to_class_name(name)
|
407
|
+
BaseListener.install_class_name(ITUNES_URI, name, "ITunes#{class_name}")
|
408
|
+
else
|
409
|
+
accessor_base = "#{ITUNES_PREFIX}_#{name.gsub(/-/, '_')}"
|
410
|
+
BaseListener.install_get_text_element(ITUNES_URI, name, accessor_base)
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
data/lib/rss/maker.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
require_relative "rss"
|
3
|
+
|
4
|
+
module RSS
|
5
|
+
##
|
6
|
+
#
|
7
|
+
# Provides a set of builders for various RSS objects
|
8
|
+
#
|
9
|
+
# * Feeds
|
10
|
+
# * RSS 0.91
|
11
|
+
# * RSS 1.0
|
12
|
+
# * RSS 2.0
|
13
|
+
# * Atom 1.0
|
14
|
+
#
|
15
|
+
# * Elements
|
16
|
+
# * Atom::Entry
|
17
|
+
|
18
|
+
module Maker
|
19
|
+
|
20
|
+
# Collection of supported makers
|
21
|
+
MAKERS = {}
|
22
|
+
|
23
|
+
class << self
|
24
|
+
# Builder for an RSS object
|
25
|
+
# Creates an object of the type passed in +args+
|
26
|
+
#
|
27
|
+
# Executes the +block+ to populate elements of the created RSS object
|
28
|
+
def make(version, &block)
|
29
|
+
self[version].make(&block)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the maker for the +version+
|
33
|
+
def [](version)
|
34
|
+
maker_info = maker(version)
|
35
|
+
raise UnsupportedMakerVersionError.new(version) if maker_info.nil?
|
36
|
+
maker_info[:maker]
|
37
|
+
end
|
38
|
+
|
39
|
+
# Adds a maker to the set of supported makers
|
40
|
+
def add_maker(version, normalized_version, maker)
|
41
|
+
MAKERS[version] = {:maker => maker, :version => normalized_version}
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns collection of supported maker versions
|
45
|
+
def versions
|
46
|
+
MAKERS.keys.uniq.sort
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns collection of supported makers
|
50
|
+
def makers
|
51
|
+
MAKERS.values.collect { |info| info[:maker] }.uniq
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns true if the version is supported
|
55
|
+
def supported?(version)
|
56
|
+
versions.include?(version)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
# Can I remove this method?
|
61
|
+
def maker(version)
|
62
|
+
MAKERS[version]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
require_relative "maker/1.0"
|
69
|
+
require_relative "maker/2.0"
|
70
|
+
require_relative "maker/feed"
|
71
|
+
require_relative "maker/entry"
|
72
|
+
require_relative "maker/content"
|
73
|
+
require_relative "maker/dublincore"
|
74
|
+
require_relative "maker/slash"
|
75
|
+
require_relative "maker/syndication"
|
76
|
+
require_relative "maker/taxonomy"
|
77
|
+
require_relative "maker/trackback"
|
78
|
+
require_relative "maker/image"
|
79
|
+
require_relative "maker/itunes"
|