feed-abstract 0.0.1 → 0.0.2
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.
- data/README.rdoc +3 -3
- data/feed-abstract.gemspec +1 -1
- data/lib/feed-abstract/channel/atom.rb +30 -32
- data/lib/feed-abstract/channel/rdf.rb +47 -49
- data/lib/feed-abstract/channel/rss.rb +69 -71
- data/lib/feed-abstract/feed.rb +58 -61
- data/lib/feed-abstract/item/atom.rb +41 -43
- data/lib/feed-abstract/item/rdf.rb +38 -40
- data/lib/feed-abstract/item/rss.rb +77 -79
- data/lib/feed-abstract/items/atom.rb +11 -13
- data/lib/feed-abstract/items/rdf.rb +10 -12
- data/lib/feed-abstract/items/rss.rb +10 -12
- data/lib/feed-abstract/mixins.rb +47 -49
- data/lib/feed-abstract/version.rb +2 -4
- data/spec/feed_abstract_channel_spec.rb +1 -1
- data/spec/feed_abstract_item_spec.rb +1 -1
- data/spec/feed_abstract_spec.rb +1 -1
- metadata +2 -2
data/README.rdoc
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
=
|
1
|
+
= FeedAbstract
|
2
2
|
|
3
|
-
|
3
|
+
FeedAbstract creates a common object graph for RSS, Atom, and RDF feeds using the classes returned by RSS::Parser
|
4
4
|
|
5
5
|
== Installation
|
6
6
|
|
@@ -8,7 +8,7 @@ Feed::Abstract creates a common object graph for RSS, Atom, and RDF feeds using
|
|
8
8
|
|
9
9
|
== Usage
|
10
10
|
|
11
|
-
See
|
11
|
+
See FeedAbstract::Feed for basic examples. Also see the FeedAbstract::Channel and FeedAbstract::Item namespaces.
|
12
12
|
|
13
13
|
== Author
|
14
14
|
|
data/feed-abstract.gemspec
CHANGED
@@ -4,7 +4,7 @@ require "feed-abstract/version"
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = "feed-abstract"
|
7
|
-
s.version =
|
7
|
+
s.version = FeedAbstract::VERSION
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
9
|
s.authors = ["Daniel Collis-Puro"]
|
10
10
|
s.email = ["djcp@cyber.law.harvard.edu"]
|
@@ -1,47 +1,45 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
module
|
4
|
-
class Abstract
|
3
|
+
module FeedAbstract
|
5
4
|
|
6
|
-
|
7
|
-
|
5
|
+
# You don't want this class. You want FeedAbstract::Channel::Atom, FeedAbstract::Channel::RSS or FeedAbstract::Channel::RDF.
|
6
|
+
class Channel
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
# See FeedAbstractMixins::Atom for more instance methods.
|
9
|
+
class Atom
|
10
|
+
include FeedAbstractMixins::Atom
|
12
11
|
|
13
|
-
|
12
|
+
attr_reader :feed, :source
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
def description
|
20
|
-
@feed.subtitle.content
|
21
|
-
end
|
22
|
-
alias :subtitle :description
|
14
|
+
def initialize(feed)
|
15
|
+
@feed = @source = feed
|
16
|
+
end
|
23
17
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
18
|
+
def description
|
19
|
+
@feed.subtitle.content
|
20
|
+
end
|
21
|
+
alias :subtitle :description
|
29
22
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
23
|
+
# A string representing the application that created this feed.
|
24
|
+
def generator
|
25
|
+
return '' if @feed.generator.nil?
|
26
|
+
@feed.generator.content
|
27
|
+
end
|
35
28
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
29
|
+
# A URL (perhaps with domain, depending on input) representing an icon for the feed.
|
30
|
+
def icon
|
31
|
+
return '' if @feed.icon.nil?
|
32
|
+
@feed.icon.content
|
33
|
+
end
|
41
34
|
|
35
|
+
# A URL (perhaps with domain, depending on input) representing a logo for the feed.
|
36
|
+
def logo
|
37
|
+
return '' if @feed.logo.nil?
|
38
|
+
@feed.logo.content
|
42
39
|
end
|
40
|
+
|
43
41
|
end
|
42
|
+
end
|
44
43
|
|
45
44
|
|
46
|
-
end
|
47
45
|
end
|
@@ -1,63 +1,61 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
module
|
4
|
-
class
|
5
|
-
class
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
end
|
13
|
-
|
14
|
-
# The authors list as a string, joined with a comma.
|
15
|
-
def author
|
16
|
-
return '' if self.authors.empty?
|
17
|
-
self.authors.join(', ')
|
18
|
-
end
|
3
|
+
module FeedAbstract
|
4
|
+
class Channel
|
5
|
+
class RDF < RSS
|
6
|
+
|
7
|
+
# The authors list as an array.
|
8
|
+
def authors
|
9
|
+
return [] if @feed.channel.dc_publishers.empty?
|
10
|
+
@feed.channel.dc_publishers
|
11
|
+
end
|
19
12
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
26
|
-
''
|
27
|
-
end
|
13
|
+
# The authors list as a string, joined with a comma.
|
14
|
+
def author
|
15
|
+
return '' if self.authors.empty?
|
16
|
+
self.authors.join(', ')
|
17
|
+
end
|
28
18
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
19
|
+
# The generator of this feed.
|
20
|
+
def generator
|
21
|
+
return '' unless @feed.channel.respond_to?(:about)
|
22
|
+
if @feed.channel.about.match(/connotea/i)
|
23
|
+
return 'Connotea'
|
33
24
|
end
|
25
|
+
''
|
26
|
+
end
|
34
27
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
28
|
+
# The category list as an array.
|
29
|
+
def categories
|
30
|
+
return [] if @feed.channel.dc_subjects.empty?
|
31
|
+
@feed.channel.dc_subjects.collect{|c| c.content}
|
32
|
+
end
|
40
33
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
alias :logo :icon
|
34
|
+
# The category list as a string, joined with a comma.
|
35
|
+
def category
|
36
|
+
return '' if self.categories.empty?
|
37
|
+
self.categories.join(', ')
|
38
|
+
end
|
47
39
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
40
|
+
# A URL (with or without domain depending on input) to a icon representing this feed.
|
41
|
+
def icon
|
42
|
+
return '' if @feed.channel.image.nil?
|
43
|
+
@feed.channel.image.resource
|
44
|
+
end
|
45
|
+
alias :logo :icon
|
53
46
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
47
|
+
# Copyright info.
|
48
|
+
def rights
|
49
|
+
return '' if @feed.channel.dc_rights.nil?
|
50
|
+
@feed.channel.dc_rights
|
51
|
+
end
|
59
52
|
|
53
|
+
# A Time object representing when this feed was updated, or at least "dated" according to the RDF spec.
|
54
|
+
def updated
|
55
|
+
return '' if @feed.channel.dc_date.nil?
|
56
|
+
@feed.channel.dc_date
|
60
57
|
end
|
58
|
+
|
61
59
|
end
|
62
60
|
end
|
63
61
|
end
|
@@ -1,91 +1,89 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
module
|
4
|
-
class
|
5
|
-
class
|
6
|
-
|
7
|
-
|
8
|
-
attr_reader :feed, :source
|
3
|
+
module FeedAbstract
|
4
|
+
class Channel
|
5
|
+
class RSS
|
6
|
+
include FeedAbstractMixins::RSS
|
7
|
+
attr_reader :feed, :source
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
def title
|
15
|
-
@feed.channel.title
|
16
|
-
end
|
9
|
+
def initialize(feed)
|
10
|
+
@feed = @source = feed
|
11
|
+
end
|
17
12
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
alias :subtitle :description
|
13
|
+
def title
|
14
|
+
@feed.channel.title
|
15
|
+
end
|
22
16
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
elsif @feed.channel.link.match(/www\.delicious\.com/i)
|
28
|
-
return 'Delicious'
|
29
|
-
end
|
30
|
-
return '' if @feed.channel.generator.nil?
|
31
|
-
@feed.channel.generator
|
32
|
-
end
|
17
|
+
def description
|
18
|
+
@feed.channel.description
|
19
|
+
end
|
20
|
+
alias :subtitle :description
|
33
21
|
|
34
|
-
|
35
|
-
|
36
|
-
|
22
|
+
# The generator of this feed as a string. Sometimes a URL, sometimes a string (e.g. the application name).
|
23
|
+
def generator
|
24
|
+
if ! @feed.channel.generator.nil? && @feed.channel.generator.match(/wordpress\.org/i)
|
25
|
+
return 'WordPress'
|
26
|
+
elsif @feed.channel.link.match(/www\.delicious\.com/i)
|
27
|
+
return 'Delicious'
|
37
28
|
end
|
29
|
+
return '' if @feed.channel.generator.nil?
|
30
|
+
@feed.channel.generator
|
31
|
+
end
|
38
32
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
33
|
+
def link
|
34
|
+
return '' if @feed.channel.link.nil?
|
35
|
+
@feed.channel.link
|
36
|
+
end
|
44
37
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
38
|
+
# Copyright info.
|
39
|
+
def rights
|
40
|
+
return '' if @feed.channel.copyright.nil? && @feed.channel.dc_rights.nil?
|
41
|
+
[@feed.channel.copyright,@feed.channel.dc_rights].compact.join(' ')
|
42
|
+
end
|
50
43
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
44
|
+
# A Time object.
|
45
|
+
def updated
|
46
|
+
return '' if @feed.channel.lastBuildDate.nil?
|
47
|
+
@feed.channel.lastBuildDate
|
48
|
+
end
|
56
49
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
50
|
+
# A globally unique ID for this feed. A URL in this case.
|
51
|
+
def guid
|
52
|
+
return '' if @feed.channel.link.nil?
|
53
|
+
@feed.channel.link
|
54
|
+
end
|
62
55
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
56
|
+
# The authors (a merge of the RSS managingEditor and dc:publisher elements) as an array.
|
57
|
+
def authors
|
58
|
+
return [] if @feed.channel.managingEditor.nil? && @feed.channel.dc_publishers.empty?
|
59
|
+
[@feed.channel.managingEditor, @feed.channel.dc_publishers].flatten.uniq
|
60
|
+
end
|
68
61
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
62
|
+
# The author list joined with a comma.
|
63
|
+
def author
|
64
|
+
return '' if self.authors.empty?
|
65
|
+
self.authors.join(', ')
|
66
|
+
end
|
74
67
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
68
|
+
# The category list (a merge of the RSS category and dc:subject elements) as an array.
|
69
|
+
def categories
|
70
|
+
return [] if @feed.channel.categories.empty? && @feed.channel.dc_subjects.empty?
|
71
|
+
[@feed.channel.categories, @feed.channel.dc_subjects].flatten.uniq.collect{|c| c.content}
|
72
|
+
end
|
80
73
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
alias :logo :icon
|
74
|
+
# The category list as a string, joined with a comma.
|
75
|
+
def category
|
76
|
+
return '' if @feed.channel.categories.empty?
|
77
|
+
@feed.channel.categories.collect{|c| c.content}.join(', ')
|
78
|
+
end
|
87
79
|
|
80
|
+
# A URL to an icon representing this feed.
|
81
|
+
def icon
|
82
|
+
return '' if @feed.channel.image.nil?
|
83
|
+
@feed.channel.image.url
|
88
84
|
end
|
85
|
+
alias :logo :icon
|
86
|
+
|
89
87
|
end
|
90
88
|
end
|
91
89
|
end
|
data/lib/feed-abstract/feed.rb
CHANGED
@@ -1,74 +1,71 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
module
|
3
|
+
module FeedAbstract
|
4
4
|
class ParserError < Exception
|
5
5
|
end
|
6
6
|
|
7
|
-
class
|
7
|
+
# FeedAbstract:::Feed is the main class. It invokes RSS::Parser and negotiates which of the FeedAbstract::Channel and FeedAbstract::Item classes get dispatched to normalize the object graph of the feed you're parsing.
|
8
|
+
class Feed
|
9
|
+
attr_reader :channel, :raw_feed, :items
|
8
10
|
|
9
|
-
#
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
input = (xml.respond_to?(:read)) ? xml.read : xml
|
49
|
-
@raw_feed = RSS::Parser.parse(input,options[:do_validate], options[:ignore_unknown_element])
|
50
|
-
if @raw_feed == nil
|
51
|
-
raise Feed::ParserError
|
52
|
-
end
|
53
|
-
negotiate_channel_class
|
11
|
+
# === Parameters
|
12
|
+
# * xml - a string or object instance that responds to <b>read</b>
|
13
|
+
# * :do_validate - whether or not the feed should be validated. Passed through to RSS::Parser
|
14
|
+
# * :ignore_unknown_element - passed through to RSS::Parser
|
15
|
+
#
|
16
|
+
# === Returns
|
17
|
+
# An object with three attributes:
|
18
|
+
# * channel - an instance of FeedAbstract::Channel matching the type of feed we recognized
|
19
|
+
# * items - an array of items matching the type of feed we recognized.
|
20
|
+
# * raw_feed - the raw feed object returned by RSS::Parser, which might include RSS::Atom::Feed, RSS::RDF, or RSS::Rss
|
21
|
+
# You will most likely be using the <b>channel</b> and <b>items</b> attributes.
|
22
|
+
#
|
23
|
+
# === Notes
|
24
|
+
# If a feed can't be parsed, we'll throw a FeedAbstract::ParserError.
|
25
|
+
#
|
26
|
+
# == Examples
|
27
|
+
#
|
28
|
+
# f = FeedAbstract::Feed.new(File.open('/home/foo/xml/feed.rss2'))
|
29
|
+
# puts f.channel.title
|
30
|
+
# puts f.channel.description
|
31
|
+
#
|
32
|
+
# f.items.each do|item|
|
33
|
+
# puts item.title
|
34
|
+
# puts item.link
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# f = FeedAbstract::Feed.new(File.open('/home/foo/xml/feed.atom'))
|
38
|
+
# puts f.channel.generator
|
39
|
+
#
|
40
|
+
# puts "All tags / categories / subjects in this feed: " + f.items.collect{|i| i.categories}.flatten.uniq.sort.join(', ')
|
41
|
+
#
|
42
|
+
# f = FeedAbstract::Feed.new(Net::HTTP.get(URI.parse('http://rss.slashdot.org/Slashdot/slashdot')))
|
43
|
+
# puts f.items.collect{|i| i.link}
|
44
|
+
#
|
45
|
+
def initialize(xml = nil, options = {:do_validate => false, :ignore_unknown_element => true})
|
46
|
+
input = (xml.respond_to?(:read)) ? xml.read : xml
|
47
|
+
@raw_feed = RSS::Parser.parse(input,options[:do_validate], options[:ignore_unknown_element])
|
48
|
+
if @raw_feed == nil
|
49
|
+
raise FeedAbstract::ParserError
|
54
50
|
end
|
51
|
+
negotiate_channel_class
|
52
|
+
end
|
55
53
|
|
56
|
-
|
54
|
+
private
|
57
55
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
end
|
56
|
+
#Here's an easy extension point for custom parsers.
|
57
|
+
def negotiate_channel_class
|
58
|
+
if @raw_feed.class == RSS::Atom::Feed
|
59
|
+
@channel = Channel::Atom.new(@raw_feed)
|
60
|
+
@items = Items::Atom.new(@raw_feed)
|
61
|
+
elsif @raw_feed.class == RSS::RDF
|
62
|
+
@channel = Channel::RDF.new(@raw_feed)
|
63
|
+
@items = Items::RDF.new(@raw_feed)
|
64
|
+
else
|
65
|
+
@channel = Channel::RSS.new(@raw_feed)
|
66
|
+
@items = Items::RSS.new(@raw_feed)
|
70
67
|
end
|
71
|
-
|
72
68
|
end
|
69
|
+
|
73
70
|
end
|
74
71
|
end
|
@@ -1,50 +1,48 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
module
|
4
|
-
class Abstract
|
5
|
-
|
6
|
-
# You don't want this class. You want Feed::Abstract::Item::Atom, Feed::Abstract::Item::RSS or Feed::Abstract::Item::RDF.
|
7
|
-
class Item
|
8
|
-
|
9
|
-
# See Feed::AbstractMixins::Atom for more instance methods.
|
10
|
-
class Atom
|
11
|
-
include Feed::AbstractMixins::Atom
|
12
|
-
attr_reader :item, :source
|
13
|
-
|
14
|
-
def initialize(item)
|
15
|
-
@item = @source = item
|
16
|
-
end
|
17
|
-
|
18
|
-
# The full content of the item, most likely html.
|
19
|
-
def content
|
20
|
-
return '' if @item.content.nil?
|
21
|
-
@item.content.content
|
22
|
-
end
|
23
|
-
|
24
|
-
# The contributor list as an array.
|
25
|
-
def contributors
|
26
|
-
return [] if @item.contributors.empty?
|
27
|
-
@item.contributors.collect{|c| c.name.content}
|
28
|
-
end
|
29
|
-
|
30
|
-
#The contributor list as a strong joined with a comma.
|
31
|
-
def contributor
|
32
|
-
return '' if @item.contributors.empty?
|
33
|
-
@item.contributors.collect{|c| c.name.content}.join(', ')
|
34
|
-
end
|
35
|
-
|
36
|
-
def summary
|
37
|
-
return '' if @item.summary.nil?
|
38
|
-
@item.summary.content
|
39
|
-
end
|
40
|
-
|
41
|
-
# A Time object
|
42
|
-
def published
|
43
|
-
return '' if @item.published.nil?
|
44
|
-
@item.published.content
|
45
|
-
end
|
3
|
+
module FeedAbstract
|
46
4
|
|
5
|
+
# You don't want this class. You want FeedAbstract::Item::Atom, FeedAbstract::Item::RSS or FeedAbstract::Item::RDF.
|
6
|
+
class Item
|
7
|
+
|
8
|
+
# See FeedAbstractMixins::Atom for more instance methods.
|
9
|
+
class Atom
|
10
|
+
include FeedAbstractMixins::Atom
|
11
|
+
attr_reader :item, :source
|
12
|
+
|
13
|
+
def initialize(item)
|
14
|
+
@item = @source = item
|
15
|
+
end
|
16
|
+
|
17
|
+
# The full content of the item, most likely html.
|
18
|
+
def content
|
19
|
+
return '' if @item.content.nil?
|
20
|
+
@item.content.content
|
47
21
|
end
|
22
|
+
|
23
|
+
# The contributor list as an array.
|
24
|
+
def contributors
|
25
|
+
return [] if @item.contributors.empty?
|
26
|
+
@item.contributors.collect{|c| c.name.content}
|
27
|
+
end
|
28
|
+
|
29
|
+
#The contributor list as a strong joined with a comma.
|
30
|
+
def contributor
|
31
|
+
return '' if @item.contributors.empty?
|
32
|
+
@item.contributors.collect{|c| c.name.content}.join(', ')
|
33
|
+
end
|
34
|
+
|
35
|
+
def summary
|
36
|
+
return '' if @item.summary.nil?
|
37
|
+
@item.summary.content
|
38
|
+
end
|
39
|
+
|
40
|
+
# A Time object
|
41
|
+
def published
|
42
|
+
return '' if @item.published.nil?
|
43
|
+
@item.published.content
|
44
|
+
end
|
45
|
+
|
48
46
|
end
|
49
47
|
end
|
50
48
|
end
|
@@ -1,48 +1,46 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
module
|
4
|
-
class
|
5
|
-
class
|
6
|
-
class RDF < RSS
|
7
|
-
|
8
|
-
# The author list (from the dc:creator element) as an array.
|
9
|
-
def authors
|
10
|
-
(@item.dc_creators.empty?) ? [] : @item.dc_creators.collect{|c| c.content}
|
11
|
-
end
|
12
|
-
|
13
|
-
# The author list as a string, joined with a comma.
|
14
|
-
def author
|
15
|
-
return '' if self.authors.empty?
|
16
|
-
self.authors.join(', ')
|
17
|
-
end
|
18
|
-
|
19
|
-
# The category list (parsed from the dc:subject element) as an array.
|
20
|
-
def categories
|
21
|
-
return [] if @item.dc_subjects.empty?
|
22
|
-
@item.dc_subjects.collect{|c| c.content}
|
23
|
-
end
|
24
|
-
|
25
|
-
# The category list as a string, joined with a comma.
|
26
|
-
def category
|
27
|
-
return '' if self.categories.empty?
|
28
|
-
self.categories.join(', ')
|
29
|
-
end
|
30
|
-
|
31
|
-
# A Time object.
|
32
|
-
def updated
|
33
|
-
return '' if @item.dc_date.nil?
|
34
|
-
@item.dc_date
|
35
|
-
end
|
36
|
-
alias :published :updated
|
37
|
-
|
38
|
-
# A globally unique id, in this case probably a URL.
|
39
|
-
def guid
|
40
|
-
return '' if @item.about.nil?
|
41
|
-
@item.about
|
42
|
-
end
|
3
|
+
module FeedAbstract
|
4
|
+
class Item
|
5
|
+
class RDF < RSS
|
43
6
|
|
7
|
+
# The author list (from the dc:creator element) as an array.
|
8
|
+
def authors
|
9
|
+
(@item.dc_creators.empty?) ? [] : @item.dc_creators.collect{|c| c.content}
|
10
|
+
end
|
11
|
+
|
12
|
+
# The author list as a string, joined with a comma.
|
13
|
+
def author
|
14
|
+
return '' if self.authors.empty?
|
15
|
+
self.authors.join(', ')
|
16
|
+
end
|
44
17
|
|
18
|
+
# The category list (parsed from the dc:subject element) as an array.
|
19
|
+
def categories
|
20
|
+
return [] if @item.dc_subjects.empty?
|
21
|
+
@item.dc_subjects.collect{|c| c.content}
|
45
22
|
end
|
23
|
+
|
24
|
+
# The category list as a string, joined with a comma.
|
25
|
+
def category
|
26
|
+
return '' if self.categories.empty?
|
27
|
+
self.categories.join(', ')
|
28
|
+
end
|
29
|
+
|
30
|
+
# A Time object.
|
31
|
+
def updated
|
32
|
+
return '' if @item.dc_date.nil?
|
33
|
+
@item.dc_date
|
34
|
+
end
|
35
|
+
alias :published :updated
|
36
|
+
|
37
|
+
# A globally unique id, in this case probably a URL.
|
38
|
+
def guid
|
39
|
+
return '' if @item.about.nil?
|
40
|
+
@item.about
|
41
|
+
end
|
42
|
+
|
43
|
+
|
46
44
|
end
|
47
45
|
end
|
48
46
|
end
|
@@ -1,86 +1,84 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
module
|
4
|
-
class
|
5
|
-
class
|
6
|
-
class RSS
|
7
|
-
|
8
|
-
include Feed::AbstractMixins::RSS
|
9
|
-
attr_reader :item, :source
|
10
|
-
|
11
|
-
def initialize(item)
|
12
|
-
@item = @source = item
|
13
|
-
end
|
14
|
-
|
15
|
-
def title
|
16
|
-
return '' if @item.title.nil?
|
17
|
-
@item.title
|
18
|
-
end
|
19
|
-
|
20
|
-
# The full content of this item, theoretically.
|
21
|
-
def content
|
22
|
-
return '' if @item.content_encoded.nil?
|
23
|
-
@item.content_encoded
|
24
|
-
end
|
25
|
-
|
26
|
-
def summary
|
27
|
-
return '' if @item.description.nil?
|
28
|
-
@item.description
|
29
|
-
end
|
30
|
-
|
31
|
-
def link
|
32
|
-
@item.link
|
33
|
-
end
|
34
|
-
|
35
|
-
# The author list (a merge of the RSS author and dc:creator elements) as an array.
|
36
|
-
def authors
|
37
|
-
[@item.author, @item.dc_creators.collect{|c| c.content}].flatten.uniq.compact
|
38
|
-
end
|
39
|
-
|
40
|
-
# The author list as a string, joined with a comma.
|
41
|
-
def author
|
42
|
-
(self.authors.empty?) ? '' : self.authors.join(', ')
|
43
|
-
end
|
44
|
-
|
45
|
-
# The contributors (parsed from the dc:contributor element) as an array.
|
46
|
-
def contributors
|
47
|
-
(@item.dc_contributors.empty?) ? [] : @item.dc_contributors
|
48
|
-
end
|
49
|
-
|
50
|
-
# The contributor list as a string joined with a comma.
|
51
|
-
def contributor
|
52
|
-
(self.contributors.empty?) ? '' : self.contributors.join(', ')
|
53
|
-
end
|
54
|
-
|
55
|
-
# The category list as an array.
|
56
|
-
def categories
|
57
|
-
return [] if @item.categories.empty?
|
58
|
-
@item.categories.collect{|c| c.content}
|
59
|
-
end
|
60
|
-
|
61
|
-
# The category list as a string, joined with a comma.
|
62
|
-
def category
|
63
|
-
return '' if @item.categories.empty?
|
64
|
-
@item.categories.collect{|c| c.content}.join(', ')
|
65
|
-
end
|
66
|
-
|
67
|
-
# Copyright info.
|
68
|
-
def rights
|
69
|
-
(@item.dc_rights.nil?) ? '' : @item.dc_rights
|
70
|
-
end
|
71
|
-
|
72
|
-
def updated
|
73
|
-
(@item.pubDate.nil?) ? '' : @item.pubDate
|
74
|
-
end
|
75
|
-
alias :published :updated
|
76
|
-
|
77
|
-
# A globally unique id.
|
78
|
-
def guid
|
79
|
-
return '' if @item.guid.nil?
|
80
|
-
@item.guid.content
|
81
|
-
end
|
3
|
+
module FeedAbstract
|
4
|
+
class Item
|
5
|
+
class RSS
|
82
6
|
|
7
|
+
include FeedAbstractMixins::RSS
|
8
|
+
attr_reader :item, :source
|
9
|
+
|
10
|
+
def initialize(item)
|
11
|
+
@item = @source = item
|
12
|
+
end
|
13
|
+
|
14
|
+
def title
|
15
|
+
return '' if @item.title.nil?
|
16
|
+
@item.title
|
17
|
+
end
|
18
|
+
|
19
|
+
# The full content of this item, theoretically.
|
20
|
+
def content
|
21
|
+
return '' if @item.content_encoded.nil?
|
22
|
+
@item.content_encoded
|
23
|
+
end
|
24
|
+
|
25
|
+
def summary
|
26
|
+
return '' if @item.description.nil?
|
27
|
+
@item.description
|
28
|
+
end
|
29
|
+
|
30
|
+
def link
|
31
|
+
@item.link
|
32
|
+
end
|
33
|
+
|
34
|
+
# The author list (a merge of the RSS author and dc:creator elements) as an array.
|
35
|
+
def authors
|
36
|
+
[@item.author, @item.dc_creators.collect{|c| c.content}].flatten.uniq.compact
|
37
|
+
end
|
38
|
+
|
39
|
+
# The author list as a string, joined with a comma.
|
40
|
+
def author
|
41
|
+
(self.authors.empty?) ? '' : self.authors.join(', ')
|
42
|
+
end
|
43
|
+
|
44
|
+
# The contributors (parsed from the dc:contributor element) as an array.
|
45
|
+
def contributors
|
46
|
+
(@item.dc_contributors.empty?) ? [] : @item.dc_contributors
|
47
|
+
end
|
48
|
+
|
49
|
+
# The contributor list as a string joined with a comma.
|
50
|
+
def contributor
|
51
|
+
(self.contributors.empty?) ? '' : self.contributors.join(', ')
|
52
|
+
end
|
53
|
+
|
54
|
+
# The category list as an array.
|
55
|
+
def categories
|
56
|
+
return [] if @item.categories.empty?
|
57
|
+
@item.categories.collect{|c| c.content}
|
58
|
+
end
|
59
|
+
|
60
|
+
# The category list as a string, joined with a comma.
|
61
|
+
def category
|
62
|
+
return '' if @item.categories.empty?
|
63
|
+
@item.categories.collect{|c| c.content}.join(', ')
|
83
64
|
end
|
65
|
+
|
66
|
+
# Copyright info.
|
67
|
+
def rights
|
68
|
+
(@item.dc_rights.nil?) ? '' : @item.dc_rights
|
69
|
+
end
|
70
|
+
|
71
|
+
def updated
|
72
|
+
(@item.pubDate.nil?) ? '' : @item.pubDate
|
73
|
+
end
|
74
|
+
alias :published :updated
|
75
|
+
|
76
|
+
# A globally unique id.
|
77
|
+
def guid
|
78
|
+
return '' if @item.guid.nil?
|
79
|
+
@item.guid.content
|
80
|
+
end
|
81
|
+
|
84
82
|
end
|
85
83
|
end
|
86
84
|
end
|
@@ -1,22 +1,20 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
module
|
4
|
-
class Abstract
|
3
|
+
module FeedAbstract
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
#Nothing interesting. The classes in this namespace map the RSS::Parser object entries/items to the proper FeedAbstract::Item classes. Perhap item-level transformations could be supported through this class in the future.
|
6
|
+
class Items
|
7
|
+
class Atom < Array
|
8
|
+
attr_reader :feed, :items
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
end
|
10
|
+
def initialize(feed)
|
11
|
+
@feed = feed
|
12
|
+
return [] if @feed.items.empty?
|
13
|
+
@feed.items.each do|item|
|
14
|
+
self << ::FeedAbstract::Item::Atom.new(item)
|
17
15
|
end
|
18
|
-
|
19
16
|
end
|
17
|
+
|
20
18
|
end
|
21
19
|
end
|
22
20
|
end
|
@@ -1,20 +1,18 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
module
|
4
|
-
class
|
5
|
-
class
|
6
|
-
|
7
|
-
attr_reader :feed, :items
|
3
|
+
module FeedAbstract
|
4
|
+
class Items
|
5
|
+
class RDF < Array
|
6
|
+
attr_reader :feed, :items
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
8
|
+
def initialize(feed)
|
9
|
+
@feed = feed
|
10
|
+
return [] if @feed.items.empty?
|
11
|
+
@feed.items.each do|item|
|
12
|
+
self << ::FeedAbstract::Item::RDF.new(item)
|
15
13
|
end
|
16
|
-
|
17
14
|
end
|
15
|
+
|
18
16
|
end
|
19
17
|
end
|
20
18
|
end
|
@@ -1,20 +1,18 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
module
|
4
|
-
class
|
5
|
-
class
|
6
|
-
|
7
|
-
attr_reader :feed, :items
|
3
|
+
module FeedAbstract
|
4
|
+
class Items
|
5
|
+
class RSS < Array
|
6
|
+
attr_reader :feed, :items
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
8
|
+
def initialize(feed)
|
9
|
+
@feed = feed
|
10
|
+
return [] if @feed.items.empty?
|
11
|
+
@feed.items.each do|item|
|
12
|
+
self << ::FeedAbstract::Item::RSS.new(item)
|
15
13
|
end
|
16
|
-
|
17
14
|
end
|
15
|
+
|
18
16
|
end
|
19
17
|
end
|
20
18
|
end
|
data/lib/feed-abstract/mixins.rb
CHANGED
@@ -1,66 +1,64 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
module
|
4
|
-
module AbstractMixins
|
3
|
+
module FeedAbstractMixins
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
# Instance methods shared between Feed::Abstract::Channel::Atom and Feed::Abstract::Item::Atom
|
10
|
-
module Atom
|
5
|
+
module RSS
|
6
|
+
end
|
11
7
|
|
12
|
-
|
13
|
-
|
14
|
-
end
|
8
|
+
# Instance methods shared between FeedAbstract::Channel::Atom and FeedAbstract::Item::Atom
|
9
|
+
module Atom
|
15
10
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
end
|
11
|
+
def title
|
12
|
+
@source.title.content
|
13
|
+
end
|
20
14
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
15
|
+
def link
|
16
|
+
return '' if @source.link.nil?
|
17
|
+
@source.link.href
|
18
|
+
end
|
26
19
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
20
|
+
# A globally unique ID to this resource, usually (but not always) a URL.
|
21
|
+
def guid
|
22
|
+
return '' if @source.id.nil?
|
23
|
+
@source.id.content
|
24
|
+
end
|
32
25
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
26
|
+
# A Time object representing when resource was updated.
|
27
|
+
def updated
|
28
|
+
return '' if @source.updated.nil?
|
29
|
+
@source.updated.content
|
30
|
+
end
|
38
31
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
32
|
+
# Copyright info.
|
33
|
+
def rights
|
34
|
+
return '' if @source.rights.nil?
|
35
|
+
@source.rights.content
|
36
|
+
end
|
44
37
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
38
|
+
# An array of author names
|
39
|
+
def authors
|
40
|
+
return [] if @source.authors.empty?
|
41
|
+
@source.authors.collect{|au| au.name.content}
|
42
|
+
end
|
50
43
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
44
|
+
# The authors list as a string, joined with a comma.
|
45
|
+
def author
|
46
|
+
return '' if self.authors.empty?
|
47
|
+
self.authors.join(', ')
|
48
|
+
end
|
56
49
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
50
|
+
# The categories list as an array.
|
51
|
+
def categories
|
52
|
+
return [] if @source.categories.empty?
|
53
|
+
@source.categories.collect{|c| c.term}
|
54
|
+
end
|
62
55
|
|
56
|
+
# The categories list as a string joined with a comma.
|
57
|
+
def category
|
58
|
+
return '' if self.categories.empty?
|
59
|
+
self.categories.join(', ')
|
63
60
|
end
|
64
61
|
|
65
62
|
end
|
63
|
+
|
66
64
|
end
|
data/spec/feed_abstract_spec.rb
CHANGED