rss 0.2.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +6 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +88 -0
  7. data/Rakefile +10 -0
  8. data/bin/console +14 -0
  9. data/bin/setup +8 -0
  10. data/lib/rss.rb +92 -0
  11. data/lib/rss/0.9.rb +462 -0
  12. data/lib/rss/1.0.rb +485 -0
  13. data/lib/rss/2.0.rb +143 -0
  14. data/lib/rss/atom.rb +1025 -0
  15. data/lib/rss/content.rb +34 -0
  16. data/lib/rss/content/1.0.rb +10 -0
  17. data/lib/rss/content/2.0.rb +12 -0
  18. data/lib/rss/converter.rb +171 -0
  19. data/lib/rss/dublincore.rb +164 -0
  20. data/lib/rss/dublincore/1.0.rb +13 -0
  21. data/lib/rss/dublincore/2.0.rb +13 -0
  22. data/lib/rss/dublincore/atom.rb +17 -0
  23. data/lib/rss/image.rb +198 -0
  24. data/lib/rss/itunes.rb +413 -0
  25. data/lib/rss/maker.rb +79 -0
  26. data/lib/rss/maker/0.9.rb +509 -0
  27. data/lib/rss/maker/1.0.rb +436 -0
  28. data/lib/rss/maker/2.0.rb +224 -0
  29. data/lib/rss/maker/atom.rb +173 -0
  30. data/lib/rss/maker/base.rb +945 -0
  31. data/lib/rss/maker/content.rb +22 -0
  32. data/lib/rss/maker/dublincore.rb +122 -0
  33. data/lib/rss/maker/entry.rb +164 -0
  34. data/lib/rss/maker/feed.rb +427 -0
  35. data/lib/rss/maker/image.rb +112 -0
  36. data/lib/rss/maker/itunes.rb +243 -0
  37. data/lib/rss/maker/slash.rb +34 -0
  38. data/lib/rss/maker/syndication.rb +19 -0
  39. data/lib/rss/maker/taxonomy.rb +119 -0
  40. data/lib/rss/maker/trackback.rb +62 -0
  41. data/lib/rss/parser.rb +589 -0
  42. data/lib/rss/rexmlparser.rb +50 -0
  43. data/lib/rss/rss.rb +1346 -0
  44. data/lib/rss/slash.rb +52 -0
  45. data/lib/rss/syndication.rb +69 -0
  46. data/lib/rss/taxonomy.rb +148 -0
  47. data/lib/rss/trackback.rb +291 -0
  48. data/lib/rss/utils.rb +200 -0
  49. data/lib/rss/xml-stylesheet.rb +106 -0
  50. data/lib/rss/xml.rb +72 -0
  51. data/lib/rss/xmlparser.rb +95 -0
  52. data/lib/rss/xmlscanner.rb +122 -0
  53. data/rss.gemspec +38 -0
  54. metadata +138 -0
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: false
2
+ require "rss/0.9"
3
+
4
+ module RSS
5
+
6
+ ##
7
+ # = RSS 2.0 support
8
+ #
9
+ # RSS has three different versions. This module contains support for version
10
+ # 2.0[http://www.rssboard.org/rss-specification]
11
+ #
12
+ # == Producing RSS 2.0
13
+ #
14
+ # Producing our own RSS feeds is easy as well. Let's make a very basic feed:
15
+ #
16
+ # require "rss"
17
+ #
18
+ # rss = RSS::Maker.make("2.0") do |maker|
19
+ # maker.channel.language = "en"
20
+ # maker.channel.author = "matz"
21
+ # maker.channel.updated = Time.now.to_s
22
+ # maker.channel.link = "http://www.ruby-lang.org/en/feeds/news.rss"
23
+ # maker.channel.title = "Example Feed"
24
+ # maker.channel.description = "A longer description of my feed."
25
+ # maker.items.new_item do |item|
26
+ # item.link = "http://www.ruby-lang.org/en/news/2010/12/25/ruby-1-9-2-p136-is-released/"
27
+ # item.title = "Ruby 1.9.2-p136 is released"
28
+ # item.updated = Time.now.to_s
29
+ # end
30
+ # end
31
+ #
32
+ # puts rss
33
+ #
34
+ # As you can see, this is a very Builder-like DSL. This code will spit out an
35
+ # RSS 2.0 feed with one item. If we needed a second item, we'd make another
36
+ # block with maker.items.new_item and build a second one.
37
+ class Rss
38
+
39
+ class Channel
40
+
41
+ [
42
+ ["generator"],
43
+ ["ttl", :integer],
44
+ ].each do |name, type|
45
+ install_text_element(name, "", "?", name, type)
46
+ end
47
+
48
+ [
49
+ %w(category categories),
50
+ ].each do |name, plural_name|
51
+ install_have_children_element(name, "", "*", name, plural_name)
52
+ end
53
+
54
+ [
55
+ ["image", "?"],
56
+ ["language", "?"],
57
+ ].each do |name, occurs|
58
+ install_model(name, "", occurs)
59
+ end
60
+
61
+ Category = Item::Category
62
+
63
+ class Item
64
+
65
+ [
66
+ ["comments", "?"],
67
+ ["author", "?"],
68
+ ].each do |name, occurs|
69
+ install_text_element(name, "", occurs)
70
+ end
71
+
72
+ [
73
+ ["pubDate", '?'],
74
+ ].each do |name, occurs|
75
+ install_date_element(name, "", occurs, name, 'rfc822')
76
+ end
77
+ alias date pubDate
78
+ alias date= pubDate=
79
+
80
+ [
81
+ ["guid", '?'],
82
+ ].each do |name, occurs|
83
+ install_have_child_element(name, "", occurs)
84
+ end
85
+
86
+ private
87
+ alias _setup_maker_element setup_maker_element
88
+ def setup_maker_element(item)
89
+ _setup_maker_element(item)
90
+ @guid.setup_maker(item) if @guid
91
+ end
92
+
93
+ class Guid < Element
94
+
95
+ include RSS09
96
+
97
+ [
98
+ ["isPermaLink", "", false, :boolean]
99
+ ].each do |name, uri, required, type|
100
+ install_get_attribute(name, uri, required, type)
101
+ end
102
+
103
+ content_setup
104
+
105
+ def initialize(*args)
106
+ if Utils.element_initialize_arguments?(args)
107
+ super
108
+ else
109
+ super()
110
+ self.isPermaLink = args[0]
111
+ self.content = args[1]
112
+ end
113
+ end
114
+
115
+ alias_method :_PermaLink?, :PermaLink?
116
+ private :_PermaLink?
117
+ def PermaLink?
118
+ perma = _PermaLink?
119
+ perma or perma.nil?
120
+ end
121
+
122
+ private
123
+ def maker_target(item)
124
+ item.guid
125
+ end
126
+
127
+ def setup_maker_attributes(guid)
128
+ guid.isPermaLink = isPermaLink
129
+ guid.content = content
130
+ end
131
+ end
132
+
133
+ end
134
+
135
+ end
136
+
137
+ end
138
+
139
+ RSS09::ELEMENTS.each do |name|
140
+ BaseListener.install_get_text_element("", name, name)
141
+ end
142
+
143
+ end
@@ -0,0 +1,1025 @@
1
+ # frozen_string_literal: false
2
+ require_relative 'parser'
3
+
4
+ module RSS
5
+ ##
6
+ # Atom is an XML-based document format that is used to describe 'feeds' of related information.
7
+ # A typical use is in a news feed where the information is periodically updated and which users
8
+ # can subscribe to. The Atom format is described in http://tools.ietf.org/html/rfc4287
9
+ #
10
+ # The Atom module provides support in reading and creating feeds.
11
+ #
12
+ # See the RSS module for examples consuming and creating feeds.
13
+ module Atom
14
+
15
+ ##
16
+ # The Atom URI W3C Namespace
17
+
18
+ URI = "http://www.w3.org/2005/Atom"
19
+
20
+ ##
21
+ # The XHTML URI W3C Namespace
22
+
23
+ XHTML_URI = "http://www.w3.org/1999/xhtml"
24
+
25
+ module CommonModel
26
+ NSPOOL = {}
27
+ ELEMENTS = []
28
+
29
+ def self.append_features(klass)
30
+ super
31
+ klass.install_must_call_validator("atom", URI)
32
+ [
33
+ ["lang", :xml],
34
+ ["base", :xml],
35
+ ].each do |name, uri, required|
36
+ klass.install_get_attribute(name, uri, required, [nil, :inherit])
37
+ end
38
+ klass.class_eval do
39
+ class << self
40
+ # Returns the Atom URI W3C Namespace
41
+ def required_uri
42
+ URI
43
+ end
44
+
45
+ # Returns true
46
+ def need_parent?
47
+ true
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ module ContentModel
55
+ module ClassMethods
56
+ def content_type
57
+ @content_type ||= nil
58
+ end
59
+ end
60
+
61
+ class << self
62
+ def append_features(klass)
63
+ super
64
+ klass.extend(ClassMethods)
65
+ klass.content_setup(klass.content_type, klass.tag_name)
66
+ end
67
+ end
68
+
69
+ def maker_target(target)
70
+ target
71
+ end
72
+
73
+ private
74
+ def setup_maker_element_writer
75
+ "#{self.class.name.split(/::/).last.downcase}="
76
+ end
77
+
78
+ def setup_maker_element(target)
79
+ target.__send__(setup_maker_element_writer, content)
80
+ super
81
+ end
82
+ end
83
+
84
+ module URIContentModel
85
+ class << self
86
+ def append_features(klass)
87
+ super
88
+ klass.class_eval do
89
+ @content_type = [nil, :uri]
90
+ include(ContentModel)
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ # The TextConstruct module is used to define a Text construct Atom element,
97
+ # which is used to store small quantities of human-readable text.
98
+ #
99
+ # The TextConstruct has a type attribute, e.g. text, html, xhtml
100
+ #
101
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#text.constructs
102
+ module TextConstruct
103
+ def self.append_features(klass)
104
+ super
105
+ klass.class_eval do
106
+ [
107
+ ["type", ""],
108
+ ].each do |name, uri, required|
109
+ install_get_attribute(name, uri, required, :text_type)
110
+ end
111
+
112
+ content_setup
113
+ add_need_initialize_variable("xhtml")
114
+
115
+ class << self
116
+ def xml_getter
117
+ "xhtml"
118
+ end
119
+
120
+ def xml_setter
121
+ "xhtml="
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ attr_writer :xhtml
128
+
129
+ # Returns or builds the XHTML content.
130
+ def xhtml
131
+ return @xhtml if @xhtml.nil?
132
+ if @xhtml.is_a?(XML::Element) and
133
+ [@xhtml.name, @xhtml.uri] == ["div", XHTML_URI]
134
+ return @xhtml
135
+ end
136
+
137
+ children = @xhtml
138
+ children = [children] unless children.is_a?(Array)
139
+ XML::Element.new("div", nil, XHTML_URI,
140
+ {"xmlns" => XHTML_URI}, children)
141
+ end
142
+
143
+ # Returns true if type is "xhtml".
144
+ def have_xml_content?
145
+ @type == "xhtml"
146
+ end
147
+
148
+ # Raises a MissingTagError or NotExpectedTagError
149
+ # if the element is not properly formatted.
150
+ def atom_validate(ignore_unknown_element, tags, uri)
151
+ if have_xml_content?
152
+ if @xhtml.nil?
153
+ raise MissingTagError.new("div", tag_name)
154
+ end
155
+ unless [@xhtml.name, @xhtml.uri] == ["div", XHTML_URI]
156
+ raise NotExpectedTagError.new(@xhtml.name, @xhtml.uri, tag_name)
157
+ end
158
+ end
159
+ end
160
+
161
+ private
162
+ def maker_target(target)
163
+ target.__send__(self.class.name.split(/::/).last.downcase) {|x| x}
164
+ end
165
+
166
+ def setup_maker_attributes(target)
167
+ target.type = type
168
+ target.content = content
169
+ target.xml_content = @xhtml
170
+ end
171
+ end
172
+
173
+ # The PersonConstruct module is used to define a person Atom element that can be
174
+ # used to describe a person, corporation or similar entity.
175
+ #
176
+ # The PersonConstruct has a Name, Uri and Email child elements.
177
+ #
178
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#atomPersonConstruct
179
+ module PersonConstruct
180
+
181
+ # Adds attributes for name, uri, and email to the +klass+
182
+ def self.append_features(klass)
183
+ super
184
+ klass.class_eval do
185
+ [
186
+ ["name", nil],
187
+ ["uri", "?"],
188
+ ["email", "?"],
189
+ ].each do |tag, occurs|
190
+ install_have_attribute_element(tag, URI, occurs, nil, :content)
191
+ end
192
+ end
193
+ end
194
+
195
+ def maker_target(target)
196
+ target.__send__("new_#{self.class.name.split(/::/).last.downcase}")
197
+ end
198
+
199
+ # The name of the person or entity.
200
+ #
201
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.name
202
+ class Name < RSS::Element
203
+ include CommonModel
204
+ include ContentModel
205
+ end
206
+
207
+ # The URI of the person or entity.
208
+ #
209
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.uri
210
+ class Uri < RSS::Element
211
+ include CommonModel
212
+ include URIContentModel
213
+ end
214
+
215
+ # The email of the person or entity.
216
+ #
217
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.email
218
+ class Email < RSS::Element
219
+ include CommonModel
220
+ include ContentModel
221
+ end
222
+ end
223
+
224
+ # Element used to describe an Atom date and time in the ISO 8601 format
225
+ #
226
+ # Examples:
227
+ # * 2013-03-04T15:30:02Z
228
+ # * 2013-03-04T10:30:02-05:00
229
+ module DateConstruct
230
+ def self.append_features(klass)
231
+ super
232
+ klass.class_eval do
233
+ @content_type = :w3cdtf
234
+ include(ContentModel)
235
+ end
236
+ end
237
+
238
+ # Raises NotAvailableValueError if element content is nil
239
+ def atom_validate(ignore_unknown_element, tags, uri)
240
+ raise NotAvailableValueError.new(tag_name, "") if content.nil?
241
+ end
242
+ end
243
+
244
+ module DuplicateLinkChecker
245
+ # Checks if there are duplicate links with the same type and hreflang attributes
246
+ # that have an alternate (or empty) rel attribute
247
+ #
248
+ # Raises a TooMuchTagError if there are duplicates found
249
+ def validate_duplicate_links(links)
250
+ link_infos = {}
251
+ links.each do |link|
252
+ rel = link.rel || "alternate"
253
+ next unless rel == "alternate"
254
+ key = [link.hreflang, link.type]
255
+ if link_infos.has_key?(key)
256
+ raise TooMuchTagError.new("link", tag_name)
257
+ end
258
+ link_infos[key] = true
259
+ end
260
+ end
261
+ end
262
+
263
+ # Defines the top-level element of an Atom Feed Document.
264
+ # It consists of a number of children Entry elements,
265
+ # and has the following attributes:
266
+ #
267
+ # * author
268
+ # * categories
269
+ # * category
270
+ # * content
271
+ # * contributor
272
+ # * entries (aliased as items)
273
+ # * entry
274
+ # * generator
275
+ # * icon
276
+ # * id
277
+ # * link
278
+ # * logo
279
+ # * rights
280
+ # * subtitle
281
+ # * title
282
+ # * updated
283
+ #
284
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.feed
285
+ class Feed < RSS::Element
286
+ include RootElementMixin
287
+ include CommonModel
288
+ include DuplicateLinkChecker
289
+
290
+ install_ns('', URI)
291
+
292
+ [
293
+ ["author", "*", :children],
294
+ ["category", "*", :children, "categories"],
295
+ ["contributor", "*", :children],
296
+ ["generator", "?"],
297
+ ["icon", "?", nil, :content],
298
+ ["id", nil, nil, :content],
299
+ ["link", "*", :children],
300
+ ["logo", "?"],
301
+ ["rights", "?"],
302
+ ["subtitle", "?", nil, :content],
303
+ ["title", nil, nil, :content],
304
+ ["updated", nil, nil, :content],
305
+ ["entry", "*", :children, "entries"],
306
+ ].each do |tag, occurs, type, *args|
307
+ type ||= :child
308
+ __send__("install_have_#{type}_element",
309
+ tag, URI, occurs, tag, *args)
310
+ end
311
+
312
+ # Creates a new Atom feed
313
+ def initialize(version=nil, encoding=nil, standalone=nil)
314
+ super("1.0", version, encoding, standalone)
315
+ @feed_type = "atom"
316
+ @feed_subtype = "feed"
317
+ end
318
+
319
+ alias_method :items, :entries
320
+
321
+ # Returns true if there are any authors for the feed or any of the Entry
322
+ # child elements have an author
323
+ def have_author?
324
+ authors.any? {|author| !author.to_s.empty?} or
325
+ entries.any? {|entry| entry.have_author?(false)}
326
+ end
327
+
328
+ private
329
+ def atom_validate(ignore_unknown_element, tags, uri)
330
+ unless have_author?
331
+ raise MissingTagError.new("author", tag_name)
332
+ end
333
+ validate_duplicate_links(links)
334
+ end
335
+
336
+ def have_required_elements?
337
+ super and have_author?
338
+ end
339
+
340
+ def maker_target(maker)
341
+ maker.channel
342
+ end
343
+
344
+ def setup_maker_element(channel)
345
+ prev_dc_dates = channel.dc_dates.to_a.dup
346
+ super
347
+ channel.about = id.content if id
348
+ channel.dc_dates.replace(prev_dc_dates)
349
+ end
350
+
351
+ def setup_maker_elements(channel)
352
+ super
353
+ items = channel.maker.items
354
+ entries.each do |entry|
355
+ entry.setup_maker(items)
356
+ end
357
+ end
358
+
359
+ # PersonConstruct that contains information regarding the author
360
+ # of a Feed or Entry.
361
+ #
362
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.author
363
+ class Author < RSS::Element
364
+ include CommonModel
365
+ include PersonConstruct
366
+ end
367
+
368
+ # Contains information about a category associated with a Feed or Entry.
369
+ # It has the following attributes:
370
+ #
371
+ # * term
372
+ # * scheme
373
+ # * label
374
+ #
375
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.category
376
+ class Category < RSS::Element
377
+ include CommonModel
378
+
379
+ [
380
+ ["term", "", true],
381
+ ["scheme", "", false, [nil, :uri]],
382
+ ["label", ""],
383
+ ].each do |name, uri, required, type|
384
+ install_get_attribute(name, uri, required, type)
385
+ end
386
+
387
+ private
388
+ def maker_target(target)
389
+ target.new_category
390
+ end
391
+ end
392
+
393
+ # PersonConstruct that contains information regarding the
394
+ # contributors of a Feed or Entry.
395
+ #
396
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.contributor
397
+ class Contributor < RSS::Element
398
+ include CommonModel
399
+ include PersonConstruct
400
+ end
401
+
402
+ # Contains information on the agent used to generate the feed.
403
+ #
404
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.generator
405
+ class Generator < RSS::Element
406
+ include CommonModel
407
+ include ContentModel
408
+
409
+ [
410
+ ["uri", "", false, [nil, :uri]],
411
+ ["version", ""],
412
+ ].each do |name, uri, required, type|
413
+ install_get_attribute(name, uri, required, type)
414
+ end
415
+
416
+ private
417
+ def setup_maker_attributes(target)
418
+ target.generator do |generator|
419
+ generator.uri = uri if uri
420
+ generator.version = version if version
421
+ end
422
+ end
423
+ end
424
+
425
+ # Defines an image that provides a visual identification for a eed.
426
+ # The image should have an aspect ratio of 1:1.
427
+ #
428
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.icon
429
+ class Icon < RSS::Element
430
+ include CommonModel
431
+ include URIContentModel
432
+ end
433
+
434
+ # Defines the Universally Unique Identifier (UUID) for a Feed or Entry.
435
+ #
436
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.id
437
+ class Id < RSS::Element
438
+ include CommonModel
439
+ include URIContentModel
440
+ end
441
+
442
+ # Defines a reference to a Web resource. It has the following
443
+ # attributes:
444
+ #
445
+ # * href
446
+ # * rel
447
+ # * type
448
+ # * hreflang
449
+ # * title
450
+ # * length
451
+ #
452
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.link
453
+ class Link < RSS::Element
454
+ include CommonModel
455
+
456
+ [
457
+ ["href", "", true, [nil, :uri]],
458
+ ["rel", ""],
459
+ ["type", ""],
460
+ ["hreflang", ""],
461
+ ["title", ""],
462
+ ["length", ""],
463
+ ].each do |name, uri, required, type|
464
+ install_get_attribute(name, uri, required, type)
465
+ end
466
+
467
+ private
468
+ def maker_target(target)
469
+ target.new_link
470
+ end
471
+ end
472
+
473
+ # Defines an image that provides a visual identification for the Feed.
474
+ # The image should have an aspect ratio of 2:1 (horizontal:vertical).
475
+ #
476
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.logo
477
+ class Logo < RSS::Element
478
+ include CommonModel
479
+ include URIContentModel
480
+
481
+ def maker_target(target)
482
+ target.maker.image
483
+ end
484
+
485
+ private
486
+ def setup_maker_element_writer
487
+ "url="
488
+ end
489
+ end
490
+
491
+ # TextConstruct that contains copyright information regarding
492
+ # the content in an Entry or Feed. It should not be used to
493
+ # convey machine readable licensing information.
494
+ #
495
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.rights
496
+ class Rights < RSS::Element
497
+ include CommonModel
498
+ include TextConstruct
499
+ end
500
+
501
+ # TextConstruct that conveys a description or subtitle for a Feed.
502
+ #
503
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.subtitle
504
+ class Subtitle < RSS::Element
505
+ include CommonModel
506
+ include TextConstruct
507
+ end
508
+
509
+ # TextConstruct that conveys a description or title for a Feed or Entry.
510
+ #
511
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.title
512
+ class Title < RSS::Element
513
+ include CommonModel
514
+ include TextConstruct
515
+ end
516
+
517
+ # DateConstruct indicating the most recent time when a Feed or
518
+ # Entry was modified in a way the publisher considers
519
+ # significant.
520
+ #
521
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.updated
522
+ class Updated < RSS::Element
523
+ include CommonModel
524
+ include DateConstruct
525
+ end
526
+
527
+ # Defines a child Atom Entry element of an Atom Feed element.
528
+ # It has the following attributes:
529
+ #
530
+ # * author
531
+ # * category
532
+ # * categories
533
+ # * content
534
+ # * contributor
535
+ # * id
536
+ # * link
537
+ # * published
538
+ # * rights
539
+ # * source
540
+ # * summary
541
+ # * title
542
+ # * updated
543
+ #
544
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.entry
545
+ class Entry < RSS::Element
546
+ include CommonModel
547
+ include DuplicateLinkChecker
548
+
549
+ [
550
+ ["author", "*", :children],
551
+ ["category", "*", :children, "categories"],
552
+ ["content", "?", :child],
553
+ ["contributor", "*", :children],
554
+ ["id", nil, nil, :content],
555
+ ["link", "*", :children],
556
+ ["published", "?", :child, :content],
557
+ ["rights", "?", :child],
558
+ ["source", "?"],
559
+ ["summary", "?", :child],
560
+ ["title", nil],
561
+ ["updated", nil, :child, :content],
562
+ ].each do |tag, occurs, type, *args|
563
+ type ||= :attribute
564
+ __send__("install_have_#{type}_element",
565
+ tag, URI, occurs, tag, *args)
566
+ end
567
+
568
+ # Returns whether any of the following are true:
569
+ #
570
+ # * There are any authors in the feed
571
+ # * If the parent element has an author and the +check_parent+
572
+ # parameter was given.
573
+ # * There is a source element that has an author
574
+ def have_author?(check_parent=true)
575
+ authors.any? {|author| !author.to_s.empty?} or
576
+ (check_parent and @parent and @parent.have_author?) or
577
+ (source and source.have_author?)
578
+ end
579
+
580
+ private
581
+ def atom_validate(ignore_unknown_element, tags, uri)
582
+ unless have_author?
583
+ raise MissingTagError.new("author", tag_name)
584
+ end
585
+ validate_duplicate_links(links)
586
+ end
587
+
588
+ def have_required_elements?
589
+ super and have_author?
590
+ end
591
+
592
+ def maker_target(items)
593
+ if items.respond_to?("items")
594
+ # For backward compatibility
595
+ items = items.items
596
+ end
597
+ items.new_item
598
+ end
599
+
600
+ # Feed::Author
601
+ Author = Feed::Author
602
+ # Feed::Category
603
+ Category = Feed::Category
604
+
605
+ # Contains or links to the content of the Entry.
606
+ # It has the following attributes:
607
+ #
608
+ # * type
609
+ # * src
610
+ #
611
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.content
612
+ class Content < RSS::Element
613
+ include CommonModel
614
+
615
+ class << self
616
+ def xml_setter
617
+ "xml="
618
+ end
619
+
620
+ def xml_getter
621
+ "xml"
622
+ end
623
+ end
624
+
625
+ [
626
+ ["type", ""],
627
+ ["src", "", false, [nil, :uri]],
628
+ ].each do |name, uri, required, type|
629
+ install_get_attribute(name, uri, required, type)
630
+ end
631
+
632
+ content_setup
633
+ add_need_initialize_variable("xml")
634
+
635
+ # Returns the element content in XML.
636
+ attr_writer :xml
637
+
638
+ # Returns true if the element has inline XML content.
639
+ def have_xml_content?
640
+ inline_xhtml? or inline_other_xml?
641
+ end
642
+
643
+ # Returns or builds the element content in XML.
644
+ def xml
645
+ return @xml unless inline_xhtml?
646
+ return @xml if @xml.nil?
647
+ if @xml.is_a?(XML::Element) and
648
+ [@xml.name, @xml.uri] == ["div", XHTML_URI]
649
+ return @xml
650
+ end
651
+
652
+ children = @xml
653
+ children = [children] unless children.is_a?(Array)
654
+ XML::Element.new("div", nil, XHTML_URI,
655
+ {"xmlns" => XHTML_URI}, children)
656
+ end
657
+
658
+ # Returns the element content in XHTML.
659
+ def xhtml
660
+ if inline_xhtml?
661
+ xml
662
+ else
663
+ nil
664
+ end
665
+ end
666
+
667
+ # Raises a MissingAttributeError, NotAvailableValueError,
668
+ # MissingTagError or NotExpectedTagError if the element is
669
+ # not properly formatted.
670
+ def atom_validate(ignore_unknown_element, tags, uri)
671
+ if out_of_line?
672
+ raise MissingAttributeError.new(tag_name, "type") if @type.nil?
673
+ unless (content.nil? or content.empty?)
674
+ raise NotAvailableValueError.new(tag_name, content)
675
+ end
676
+ elsif inline_xhtml?
677
+ if @xml.nil?
678
+ raise MissingTagError.new("div", tag_name)
679
+ end
680
+ unless @xml.name == "div" and @xml.uri == XHTML_URI
681
+ raise NotExpectedTagError.new(@xml.name, @xml.uri, tag_name)
682
+ end
683
+ end
684
+ end
685
+
686
+ # Returns true if the element contains inline content
687
+ # that has a text or HTML media type, or no media type at all.
688
+ def inline_text?
689
+ !out_of_line? and [nil, "text", "html"].include?(@type)
690
+ end
691
+
692
+ # Returns true if the element contains inline content that
693
+ # has a HTML media type.
694
+ def inline_html?
695
+ return false if out_of_line?
696
+ @type == "html" or mime_split == ["text", "html"]
697
+ end
698
+
699
+ # Returns true if the element contains inline content that
700
+ # has a XHTML media type.
701
+ def inline_xhtml?
702
+ !out_of_line? and @type == "xhtml"
703
+ end
704
+
705
+ # Returns true if the element contains inline content that
706
+ # has a MIME media type.
707
+ def inline_other?
708
+ return false if out_of_line?
709
+ media_type, subtype = mime_split
710
+ return false if media_type.nil? or subtype.nil?
711
+ true
712
+ end
713
+
714
+ # Returns true if the element contains inline content that
715
+ # has a text media type.
716
+ def inline_other_text?
717
+ return false unless inline_other?
718
+ return false if inline_other_xml?
719
+
720
+ media_type, = mime_split
721
+ return true if "text" == media_type.downcase
722
+ false
723
+ end
724
+
725
+ # Returns true if the element contains inline content that
726
+ # has a XML media type.
727
+ def inline_other_xml?
728
+ return false unless inline_other?
729
+
730
+ media_type, subtype = mime_split
731
+ normalized_mime_type = "#{media_type}/#{subtype}".downcase
732
+ if /(?:\+xml|^xml)$/ =~ subtype or
733
+ %w(text/xml-external-parsed-entity
734
+ application/xml-external-parsed-entity
735
+ application/xml-dtd).find {|x| x == normalized_mime_type}
736
+ return true
737
+ end
738
+ false
739
+ end
740
+
741
+ # Returns true if the element contains inline content
742
+ # encoded in base64.
743
+ def inline_other_base64?
744
+ inline_other? and !inline_other_text? and !inline_other_xml?
745
+ end
746
+
747
+ # Returns true if the element contains linked content.
748
+ def out_of_line?
749
+ not @src.nil?
750
+ end
751
+
752
+ # Splits the type attribute into an array, e.g. ["text", "xml"]
753
+ def mime_split
754
+ media_type = subtype = nil
755
+ if /\A\s*([a-z]+)\/([a-z\+]+)\s*(?:;.*)?\z/i =~ @type.to_s
756
+ media_type = $1.downcase
757
+ subtype = $2.downcase
758
+ end
759
+ [media_type, subtype]
760
+ end
761
+
762
+ # Returns true if the content needs to be encoded in base64.
763
+ def need_base64_encode?
764
+ inline_other_base64?
765
+ end
766
+
767
+ private
768
+ def empty_content?
769
+ out_of_line? or super
770
+ end
771
+ end
772
+
773
+ # Feed::Contributor
774
+ Contributor = Feed::Contributor
775
+ # Feed::Id
776
+ Id = Feed::Id
777
+ # Feed::Link
778
+ Link = Feed::Link
779
+
780
+ # DateConstruct that usually indicates the time of the initial
781
+ # creation of an Entry.
782
+ #
783
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.published
784
+ class Published < RSS::Element
785
+ include CommonModel
786
+ include DateConstruct
787
+ end
788
+
789
+ # Feed::Rights
790
+ Rights = Feed::Rights
791
+
792
+ # Defines a Atom Source element. It has the following attributes:
793
+ #
794
+ # * author
795
+ # * category
796
+ # * categories
797
+ # * content
798
+ # * contributor
799
+ # * generator
800
+ # * icon
801
+ # * id
802
+ # * link
803
+ # * logo
804
+ # * rights
805
+ # * subtitle
806
+ # * title
807
+ # * updated
808
+ #
809
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.source
810
+ class Source < RSS::Element
811
+ include CommonModel
812
+
813
+ [
814
+ ["author", "*", :children],
815
+ ["category", "*", :children, "categories"],
816
+ ["contributor", "*", :children],
817
+ ["generator", "?"],
818
+ ["icon", "?"],
819
+ ["id", "?", nil, :content],
820
+ ["link", "*", :children],
821
+ ["logo", "?"],
822
+ ["rights", "?"],
823
+ ["subtitle", "?"],
824
+ ["title", "?"],
825
+ ["updated", "?", nil, :content],
826
+ ].each do |tag, occurs, type, *args|
827
+ type ||= :attribute
828
+ __send__("install_have_#{type}_element",
829
+ tag, URI, occurs, tag, *args)
830
+ end
831
+
832
+ # Returns true if the Source element has an author.
833
+ def have_author?
834
+ !author.to_s.empty?
835
+ end
836
+
837
+ # Feed::Author
838
+ Author = Feed::Author
839
+ # Feed::Category
840
+ Category = Feed::Category
841
+ # Feed::Contributor
842
+ Contributor = Feed::Contributor
843
+ # Feed::Generator
844
+ Generator = Feed::Generator
845
+ # Feed::Icon
846
+ Icon = Feed::Icon
847
+ # Feed::Id
848
+ Id = Feed::Id
849
+ # Feed::Link
850
+ Link = Feed::Link
851
+ # Feed::Logo
852
+ Logo = Feed::Logo
853
+ # Feed::Rights
854
+ Rights = Feed::Rights
855
+ # Feed::Subtitle
856
+ Subtitle = Feed::Subtitle
857
+ # Feed::Title
858
+ Title = Feed::Title
859
+ # Feed::Updated
860
+ Updated = Feed::Updated
861
+ end
862
+
863
+ # TextConstruct that describes a summary of the Entry.
864
+ #
865
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.summary
866
+ class Summary < RSS::Element
867
+ include CommonModel
868
+ include TextConstruct
869
+ end
870
+
871
+ # Feed::Title
872
+ Title = Feed::Title
873
+ # Feed::Updated
874
+ Updated = Feed::Updated
875
+ end
876
+ end
877
+
878
+ # Defines a top-level Atom Entry element,
879
+ # used as the document element of a stand-alone Atom Entry Document.
880
+ # It has the following attributes:
881
+ #
882
+ # * author
883
+ # * category
884
+ # * categories
885
+ # * content
886
+ # * contributor
887
+ # * id
888
+ # * link
889
+ # * published
890
+ # * rights
891
+ # * source
892
+ # * summary
893
+ # * title
894
+ # * updated
895
+ #
896
+ # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.entry]
897
+ class Entry < RSS::Element
898
+ include RootElementMixin
899
+ include CommonModel
900
+ include DuplicateLinkChecker
901
+
902
+ [
903
+ ["author", "*", :children],
904
+ ["category", "*", :children, "categories"],
905
+ ["content", "?"],
906
+ ["contributor", "*", :children],
907
+ ["id", nil, nil, :content],
908
+ ["link", "*", :children],
909
+ ["published", "?", :child, :content],
910
+ ["rights", "?"],
911
+ ["source", "?"],
912
+ ["summary", "?"],
913
+ ["title", nil],
914
+ ["updated", nil, nil, :content],
915
+ ].each do |tag, occurs, type, *args|
916
+ type ||= :attribute
917
+ __send__("install_have_#{type}_element",
918
+ tag, URI, occurs, tag, *args)
919
+ end
920
+
921
+ # Creates a new Atom Entry element.
922
+ def initialize(version=nil, encoding=nil, standalone=nil)
923
+ super("1.0", version, encoding, standalone)
924
+ @feed_type = "atom"
925
+ @feed_subtype = "entry"
926
+ end
927
+
928
+ # Returns the Entry in an array.
929
+ def items
930
+ [self]
931
+ end
932
+
933
+ # Sets up the +maker+ for constructing Entry elements.
934
+ def setup_maker(maker)
935
+ maker = maker.maker if maker.respond_to?("maker")
936
+ super(maker)
937
+ end
938
+
939
+ # Returns where there are any authors present or there is a
940
+ # source with an author.
941
+ def have_author?
942
+ authors.any? {|author| !author.to_s.empty?} or
943
+ (source and source.have_author?)
944
+ end
945
+
946
+ private
947
+ def atom_validate(ignore_unknown_element, tags, uri)
948
+ unless have_author?
949
+ raise MissingTagError.new("author", tag_name)
950
+ end
951
+ validate_duplicate_links(links)
952
+ end
953
+
954
+ def have_required_elements?
955
+ super and have_author?
956
+ end
957
+
958
+ def maker_target(maker)
959
+ maker.items.new_item
960
+ end
961
+
962
+ # Feed::Entry::Author
963
+ Author = Feed::Entry::Author
964
+ # Feed::Entry::Category
965
+ Category = Feed::Entry::Category
966
+ # Feed::Entry::Content
967
+ Content = Feed::Entry::Content
968
+ # Feed::Entry::Contributor
969
+ Contributor = Feed::Entry::Contributor
970
+ # Feed::Entry::Id
971
+ Id = Feed::Entry::Id
972
+ # Feed::Entry::Link
973
+ Link = Feed::Entry::Link
974
+ # Feed::Entry::Published
975
+ Published = Feed::Entry::Published
976
+ # Feed::Entry::Rights
977
+ Rights = Feed::Entry::Rights
978
+ # Feed::Entry::Source
979
+ Source = Feed::Entry::Source
980
+ # Feed::Entry::Summary
981
+ Summary = Feed::Entry::Summary
982
+ # Feed::Entry::Title
983
+ Title = Feed::Entry::Title
984
+ # Feed::Entry::Updated
985
+ Updated = Feed::Entry::Updated
986
+ end
987
+ end
988
+
989
+ Atom::CommonModel::ELEMENTS.each do |name|
990
+ BaseListener.install_get_text_element(Atom::URI, name, "#{name}=")
991
+ end
992
+
993
+ module ListenerMixin
994
+ private
995
+ def initial_start_feed(tag_name, prefix, attrs, ns)
996
+ check_ns(tag_name, prefix, ns, Atom::URI, false)
997
+
998
+ @rss = Atom::Feed.new(@version, @encoding, @standalone)
999
+ @rss.do_validate = @do_validate
1000
+ @rss.xml_stylesheets = @xml_stylesheets
1001
+ @rss.lang = attrs["xml:lang"]
1002
+ @rss.base = attrs["xml:base"]
1003
+ @last_element = @rss
1004
+ pr = Proc.new do |text, tags|
1005
+ @rss.validate_for_stream(tags) if @do_validate
1006
+ end
1007
+ @proc_stack.push(pr)
1008
+ end
1009
+
1010
+ def initial_start_entry(tag_name, prefix, attrs, ns)
1011
+ check_ns(tag_name, prefix, ns, Atom::URI, false)
1012
+
1013
+ @rss = Atom::Entry.new(@version, @encoding, @standalone)
1014
+ @rss.do_validate = @do_validate
1015
+ @rss.xml_stylesheets = @xml_stylesheets
1016
+ @rss.lang = attrs["xml:lang"]
1017
+ @rss.base = attrs["xml:base"]
1018
+ @last_element = @rss
1019
+ pr = Proc.new do |text, tags|
1020
+ @rss.validate_for_stream(tags) if @do_validate
1021
+ end
1022
+ @proc_stack.push(pr)
1023
+ end
1024
+ end
1025
+ end