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