feed-normalizer 1.5.1 → 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,137 +1,138 @@
1
- require 'simple-rss'
2
-
3
- # Monkey patches for outstanding issues logged in the simple-rss project.
4
- # * Add support for issued time field:
5
- # http://rubyforge.org/tracker/index.php?func=detail&aid=13980&group_id=893&atid=3517
6
- # * The '+' symbol is lost when escaping fields.
7
- # http://rubyforge.org/tracker/index.php?func=detail&aid=10852&group_id=893&atid=3517
8
- #
9
- class SimpleRSS
10
- @@item_tags << :issued
11
-
12
- undef clean_content
13
- def clean_content(tag, attrs, content)
14
- content = content.to_s
15
- case tag
16
- when :pubDate, :lastBuildDate, :published, :updated, :expirationDate, :modified, :'dc:date', :issued
17
- Time.parse(content) rescue unescape(content)
18
- when :author, :contributor, :skipHours, :skipDays
19
- unescape(content.gsub(/<.*?>/,''))
20
- else
21
- content.empty? && "#{attrs} " =~ /href=['"]?([^'"]*)['" ]/mi ? $1.strip : unescape(content)
22
- end
23
- end
24
-
25
- undef unescape
26
- def unescape(s)
27
- if s =~ /^(<!\[CDATA\[|\]\]>)/
28
- # Raw HTML is inside the CDATA, so just remove the CDATA wrapper.
29
- s.gsub(/(<!\[CDATA\[|\]\]>)/,'').strip
30
- elsif s =~ /[<>]/
31
- # Already looks like HTML.
32
- s
33
- else
34
- # Make it HTML.
35
- FeedNormalizer::HtmlCleaner.unescapeHTML(s)
36
- end
37
- end
38
- end
39
-
40
- module FeedNormalizer
41
-
42
- # The SimpleRSS parser can handle both RSS and Atom feeds.
43
- class SimpleRssParser < Parser
44
-
45
- def self.parser
46
- SimpleRSS
47
- end
48
-
49
- def self.parse(xml, loose)
50
- begin
51
- atomrss = parser.parse(xml)
52
- rescue Exception => e
53
- #puts "Parser #{parser} failed because #{e.message.gsub("\n",', ')}"
54
- return nil
55
- end
56
-
57
- package(atomrss)
58
- end
59
-
60
- # Fairly low priority; a slower, liberal parser.
61
- def self.priority
62
- 900
63
- end
64
-
65
- protected
66
-
67
- def self.package(atomrss)
68
- feed = Feed.new(self)
69
-
70
- # root elements
71
- feed_mapping = {
72
- :generator => :generator,
73
- :title => :title,
74
- :last_updated => [:updated, :lastBuildDate, :pubDate, :dc_date],
75
- :copyright => [:copyright, :rights],
76
- :authors => [:author, :webMaster, :managingEditor, :contributor],
77
- :urls => :link,
78
- :description => [:description, :subtitle],
79
- :ttl => :ttl
80
- }
81
-
82
- map_functions!(feed_mapping, atomrss, feed)
83
-
84
- # custom channel elements
85
- feed.id = feed_id(atomrss)
86
- feed.image = image(atomrss)
87
-
88
-
89
- # entry elements
90
- entry_mapping = {
91
- :date_published => [:pubDate, :published, :dc_date, :issued],
92
- :urls => :link,
93
- :description => [:description, :summary],
94
- :content => [:content, :content_encoded, :description],
95
- :title => :title,
96
- :authors => [:author, :contributor, :dc_creator],
97
- :categories => :category,
98
- :last_updated => [:updated, :dc_date, :pubDate]
99
- }
100
-
101
- atomrss.entries.each do |atomrss_entry|
102
- feed_entry = Entry.new
103
- map_functions!(entry_mapping, atomrss_entry, feed_entry)
104
-
105
- # custom entry elements
106
- feed_entry.id = atomrss_entry.guid || atomrss_entry[:id] # entries are a Hash..
107
- feed_entry.copyright = atomrss_entry.copyright || (atomrss.respond_to?(:copyright) ? atomrss.copyright : nil)
108
-
109
- feed.entries << feed_entry
110
- end
111
-
112
- feed
113
- end
114
-
115
- def self.image(parser)
116
- if parser.respond_to?(:image) && parser.image
117
- if parser.image =~ /<url>/ # RSS image contains an <url> spec
118
- parser.image.scan(/<url>(.*?)<\/url>/).to_s
119
- else
120
- parser.image # Atom contains just the url
121
- end
122
- elsif parser.respond_to?(:logo) && parser.logo
123
- parser.logo
124
- end
125
- end
126
-
127
- def self.feed_id(parser)
128
- overridden_value(parser, :id) || ("#{parser.link}" if parser.respond_to?(:link))
129
- end
130
-
131
- # gets the value returned from the method if it overriden, otherwise nil.
132
- def self.overridden_value(object, method)
133
- object.class.public_instance_methods(false).include? method
134
- end
135
-
136
- end
137
- end
1
+ require 'simple-rss'
2
+
3
+ # Monkey patches for outstanding issues logged in the simple-rss project.
4
+ # * Add support for issued time field:
5
+ # http://rubyforge.org/tracker/index.php?func=detail&aid=13980&group_id=893&atid=3517
6
+ # * The '+' symbol is lost when escaping fields.
7
+ # http://rubyforge.org/tracker/index.php?func=detail&aid=10852&group_id=893&atid=3517
8
+ #
9
+ class SimpleRSS
10
+ @@item_tags << :issued
11
+
12
+ undef clean_content
13
+ def clean_content(tag, attrs, content)
14
+ content = content.to_s
15
+ case tag
16
+ when :pubDate, :lastBuildDate, :published, :updated, :expirationDate, :modified, :'dc:date', :issued
17
+ Time.parse(content) rescue unescape(content)
18
+ when :author, :contributor, :skipHours, :skipDays
19
+ unescape(content.gsub(/<.*?>/,''))
20
+ else
21
+ content.empty? && "#{attrs} " =~ /href=['"]?([^'"]*)['" ]/mi ? $1.strip : unescape(content)
22
+ end
23
+ end
24
+
25
+ undef unescape
26
+ def unescape(s)
27
+ if s =~ /^\s*(<!\[CDATA\[|\]\]>)/
28
+ # Raw HTML is inside the CDATA, so just remove the CDATA wrapper.
29
+ s.gsub(/(<!\[CDATA\[|\]\]>)/,'')
30
+ elsif s =~ /[<>]/
31
+ # Already looks like HTML.
32
+ s
33
+ else
34
+ # Make it HTML.
35
+ FeedNormalizer::HtmlCleaner.unescapeHTML(s)
36
+ end
37
+ end
38
+ end
39
+
40
+ module FeedNormalizer
41
+
42
+ # The SimpleRSS parser can handle both RSS and Atom feeds.
43
+ class SimpleRssParser < Parser
44
+
45
+ def self.parser
46
+ SimpleRSS
47
+ end
48
+
49
+ def self.parse(xml, loose)
50
+ begin
51
+ atomrss = parser.parse(xml)
52
+ rescue Exception => e
53
+ #puts "Parser #{parser} failed because #{e.message.gsub("\n",', ')}"
54
+ return nil
55
+ end
56
+
57
+ package(atomrss)
58
+ end
59
+
60
+ # Fairly low priority; a slower, liberal parser.
61
+ def self.priority
62
+ 900
63
+ end
64
+
65
+ protected
66
+
67
+ def self.package(atomrss)
68
+ feed = Feed.new(self)
69
+
70
+ # root elements
71
+ feed_mapping = {
72
+ :generator => :generator,
73
+ :title => :title,
74
+ :last_updated => [:updated, :lastBuildDate, :pubDate, :dc_date],
75
+ :copyright => [:copyright, :rights],
76
+ :authors => [:author, :webMaster, :managingEditor, :contributor],
77
+ :urls => :link,
78
+ :description => [:description, :subtitle],
79
+ :ttl => :ttl
80
+ }
81
+
82
+ map_functions!(feed_mapping, atomrss, feed)
83
+
84
+ # custom channel elements
85
+ feed.id = feed_id(atomrss)
86
+ feed.image = image(atomrss)
87
+
88
+
89
+ # entry elements
90
+ entry_mapping = {
91
+ :date_published => [:pubDate, :published, :dc_date, :issued],
92
+ :urls => :link,
93
+ :enclosures => :enclosure,
94
+ :description => [:description, :summary],
95
+ :content => [:content, :content_encoded, :description],
96
+ :title => :title,
97
+ :authors => [:author, :contributor, :dc_creator],
98
+ :categories => :category,
99
+ :last_updated => [:updated, :dc_date, :pubDate]
100
+ }
101
+
102
+ atomrss.entries.each do |atomrss_entry|
103
+ feed_entry = Entry.new
104
+ map_functions!(entry_mapping, atomrss_entry, feed_entry)
105
+
106
+ # custom entry elements
107
+ feed_entry.id = atomrss_entry.guid || atomrss_entry[:id] # entries are a Hash..
108
+ feed_entry.copyright = atomrss_entry.copyright || (atomrss.respond_to?(:copyright) ? atomrss.copyright : nil)
109
+
110
+ feed.entries << feed_entry
111
+ end
112
+
113
+ feed
114
+ end
115
+
116
+ def self.image(parser)
117
+ if parser.respond_to?(:image) && parser.image
118
+ if parser.image =~ /<url>/ # RSS image contains an <url> spec
119
+ parser.image.scan(/<url>(.*?)<\/url>/).to_s
120
+ else
121
+ parser.image # Atom contains just the url
122
+ end
123
+ elsif parser.respond_to?(:logo) && parser.logo
124
+ parser.logo
125
+ end
126
+ end
127
+
128
+ def self.feed_id(parser)
129
+ overridden_value(parser, :id) || ("#{parser.link}" if parser.respond_to?(:link))
130
+ end
131
+
132
+ # gets the value returned from the method if it overriden, otherwise nil.
133
+ def self.overridden_value(object, method)
134
+ object.class.public_instance_methods(false).include? method
135
+ end
136
+
137
+ end
138
+ end
@@ -1,244 +1,245 @@
1
-
2
- module FeedNormalizer
3
-
4
- module Singular
5
-
6
- # If the method being called is a singular (in this simple case, does not
7
- # end with an 's'), then it calls the plural method, and calls the first
8
- # element. We're assuming that plural methods provide an array.
9
- #
10
- # Example:
11
- # Object contains an array called 'alphas', which looks like [:a, :b, :c].
12
- # Call object.alpha and :a is returned.
13
- def method_missing(name, *args)
14
- return self.send(:"#{name}s").first rescue super(name, *args)
15
- end
16
-
17
- def respond_to?(x, y=false)
18
- self.class::ELEMENTS.include?(x) || self.class::ELEMENTS.include?(:"#{x}s") || super(x, y)
19
- end
20
-
21
- end
22
-
23
- module ElementEquality
24
-
25
- def eql?(other)
26
- self == (other)
27
- end
28
-
29
- def ==(other)
30
- other.equal?(self) ||
31
- (other.instance_of?(self.class) &&
32
- self.class::ELEMENTS.all?{ |el| self.send(el) == other.send(el)} )
33
- end
34
-
35
- # Returns the difference between two Feed instances as a hash.
36
- # Any top-level differences in the Feed object as presented as:
37
- #
38
- # { :title => [content, other_content] }
39
- #
40
- # For differences at the items level, an array of hashes shows the diffs
41
- # on a per-entry basis. Only entries that differ will contain a hash:
42
- #
43
- # { :items => [
44
- # {:title => ["An article tile", "A new article title"]},
45
- # {:title => ["one title", "a different title"]} ]}
46
- #
47
- # If the number of items in each feed are different, then the count of each
48
- # is provided instead:
49
- #
50
- # { :items => [4,5] }
51
- #
52
- # This method can also be useful for human-readable feed comparison if
53
- # its output is dumped to YAML.
54
- def diff(other, elements = self.class::ELEMENTS)
55
- diffs = {}
56
-
57
- elements.each do |element|
58
- if other.respond_to?(element)
59
- self_value = self.send(element)
60
- other_value = other.send(element)
61
-
62
- next if self_value == other_value
63
-
64
- diffs[element] = if other_value.respond_to?(:diff)
65
- self_value.diff(other_value)
66
-
67
- elsif other_value.is_a?(Enumerable) && other_value.all?{|v| v.respond_to?(:diff)}
68
-
69
- if self_value.size != other_value.size
70
- [self_value.size, other_value.size]
71
- else
72
- enum_diffs = []
73
- self_value.each_with_index do |val, index|
74
- enum_diffs << val.diff(other_value[index], val.class::ELEMENTS)
75
- end
76
- enum_diffs.reject{|h| h.empty?}
77
- end
78
-
79
- else
80
- [other_value, self_value] unless other_value == self_value
81
- end
82
- end
83
- end
84
-
85
- diffs
86
- end
87
-
88
- end
89
-
90
- module ElementCleaner
91
- # Recursively cleans all elements in place.
92
- #
93
- # Only allow tags in whitelist. Always parse the html with a parser and delete
94
- # all tags that arent on the list.
95
- #
96
- # For feed elements that can contain HTML:
97
- # - feed.(title|description)
98
- # - feed.entries[n].(title|description|content)
99
- #
100
- def clean!
101
- self.class::SIMPLE_ELEMENTS.each do |element|
102
- val = self.send(element)
103
-
104
- send("#{element}=", (val.is_a?(Array) ?
105
- val.collect{|v| HtmlCleaner.flatten(v.to_s)} : HtmlCleaner.flatten(val.to_s)))
106
- end
107
-
108
- self.class::HTML_ELEMENTS.each do |element|
109
- send("#{element}=", HtmlCleaner.clean(self.send(element).to_s))
110
- end
111
-
112
- self.class::BLENDED_ELEMENTS.each do |element|
113
- self.send(element).collect{|v| v.clean!}
114
- end
115
- end
116
- end
117
-
118
- module TimeFix
119
- # Reparse any Time instances, due to RSS::Parser's redefinition of
120
- # certain aspects of the Time class that creates unexpected behaviour
121
- # when extending the Time class, as some common third party libraries do.
122
- # See http://code.google.com/p/feed-normalizer/issues/detail?id=13.
123
- def reparse(obj)
124
- @parsed ||= false
125
-
126
- return obj if @parsed
127
-
128
- if obj.is_a?(Time)
129
- @parsed = true
130
- Time.at(obj) rescue obj
131
- end
132
- end
133
- end
134
-
135
- module RewriteRelativeLinks
136
- def rewrite_relative_links(text, url)
137
- if host = url_host(url)
138
- text.to_s.gsub(/(href|src)=('|")\//, '\1=\2http://' + host + '/')
139
- else
140
- text
141
- end
142
- end
143
-
144
- private
145
- def url_host(url)
146
- URI.parse(url).host rescue nil
147
- end
148
- end
149
-
150
-
151
- # Represents a feed item entry.
152
- # Available fields are:
153
- # * content
154
- # * description
155
- # * title
156
- # * date_published
157
- # * urls / url
158
- # * id
159
- # * authors / author
160
- # * copyright
161
- # * categories
162
- class Entry
163
- include Singular, ElementEquality, ElementCleaner, TimeFix, RewriteRelativeLinks
164
-
165
- HTML_ELEMENTS = [:content, :description, :title]
166
- SIMPLE_ELEMENTS = [:date_published, :urls, :id, :authors, :copyright, :categories, :last_updated]
167
- BLENDED_ELEMENTS = []
168
-
169
- ELEMENTS = HTML_ELEMENTS + SIMPLE_ELEMENTS + BLENDED_ELEMENTS
170
-
171
- attr_accessor(*ELEMENTS)
172
-
173
- def initialize
174
- @urls = []
175
- @authors = []
176
- @categories = []
177
- @date_published, @content = nil
178
- end
179
-
180
- undef date_published
181
- def date_published
182
- @date_published = reparse(@date_published)
183
- end
184
-
185
- undef content
186
- def content
187
- @content = rewrite_relative_links(@content, url)
188
- end
189
-
190
- end
191
-
192
- # Represents the root element of a feed.
193
- # Available fields are:
194
- # * title
195
- # * description
196
- # * id
197
- # * last_updated
198
- # * copyright
199
- # * authors / author
200
- # * urls / url
201
- # * image
202
- # * generator
203
- # * items / channel
204
- class Feed
205
- include Singular, ElementEquality, ElementCleaner, TimeFix
206
-
207
- # Elements that can contain HTML fragments.
208
- HTML_ELEMENTS = [:title, :description]
209
-
210
- # Elements that contain 'plain' Strings, with HTML escaped.
211
- SIMPLE_ELEMENTS = [:id, :last_updated, :copyright, :authors, :urls, :image, :generator, :ttl, :skip_hours, :skip_days]
212
-
213
- # Elements that contain both HTML and escaped HTML.
214
- BLENDED_ELEMENTS = [:items]
215
-
216
- ELEMENTS = HTML_ELEMENTS + SIMPLE_ELEMENTS + BLENDED_ELEMENTS
217
-
218
- attr_accessor(*ELEMENTS)
219
- attr_accessor(:parser)
220
-
221
- alias :entries :items
222
-
223
- def initialize(wrapper)
224
- # set up associations (i.e. arrays where needed)
225
- @urls = []
226
- @authors = []
227
- @skip_hours = []
228
- @skip_days = []
229
- @items = []
230
- @parser = wrapper.parser.to_s
231
- @last_updated = nil
232
- end
233
-
234
- undef last_updated
235
- def last_updated
236
- @last_updated = reparse(@last_updated)
237
- end
238
-
239
- def channel() self end
240
-
241
- end
242
-
243
- end
244
-
1
+
2
+ module FeedNormalizer
3
+
4
+ module Singular
5
+
6
+ # If the method being called is a singular (in this simple case, does not
7
+ # end with an 's'), then it calls the plural method, and calls the first
8
+ # element. We're assuming that plural methods provide an array.
9
+ #
10
+ # Example:
11
+ # Object contains an array called 'alphas', which looks like [:a, :b, :c].
12
+ # Call object.alpha and :a is returned.
13
+ def method_missing(name, *args)
14
+ return self.send(:"#{name}s").first rescue super(name, *args)
15
+ end
16
+
17
+ def respond_to?(x, y=false)
18
+ self.class::ELEMENTS.include?(x) || self.class::ELEMENTS.include?(:"#{x}s") || super(x, y)
19
+ end
20
+
21
+ end
22
+
23
+ module ElementEquality
24
+
25
+ def eql?(other)
26
+ self == (other)
27
+ end
28
+
29
+ def ==(other)
30
+ other.equal?(self) ||
31
+ (other.instance_of?(self.class) &&
32
+ self.class::ELEMENTS.all?{ |el| self.send(el) == other.send(el)} )
33
+ end
34
+
35
+ # Returns the difference between two Feed instances as a hash.
36
+ # Any top-level differences in the Feed object as presented as:
37
+ #
38
+ # { :title => [content, other_content] }
39
+ #
40
+ # For differences at the items level, an array of hashes shows the diffs
41
+ # on a per-entry basis. Only entries that differ will contain a hash:
42
+ #
43
+ # { :items => [
44
+ # {:title => ["An article tile", "A new article title"]},
45
+ # {:title => ["one title", "a different title"]} ]}
46
+ #
47
+ # If the number of items in each feed are different, then the count of each
48
+ # is provided instead:
49
+ #
50
+ # { :items => [4,5] }
51
+ #
52
+ # This method can also be useful for human-readable feed comparison if
53
+ # its output is dumped to YAML.
54
+ def diff(other, elements = self.class::ELEMENTS)
55
+ diffs = {}
56
+
57
+ elements.each do |element|
58
+ if other.respond_to?(element)
59
+ self_value = self.send(element)
60
+ other_value = other.send(element)
61
+
62
+ next if self_value == other_value
63
+
64
+ diffs[element] = if other_value.respond_to?(:diff)
65
+ self_value.diff(other_value)
66
+
67
+ elsif other_value.is_a?(Enumerable) && other_value.all?{|v| v.respond_to?(:diff)}
68
+
69
+ if self_value.size != other_value.size
70
+ [self_value.size, other_value.size]
71
+ else
72
+ enum_diffs = []
73
+ self_value.each_with_index do |val, index|
74
+ enum_diffs << val.diff(other_value[index], val.class::ELEMENTS)
75
+ end
76
+ enum_diffs.reject{|h| h.empty?}
77
+ end
78
+
79
+ else
80
+ [other_value, self_value] unless other_value == self_value
81
+ end
82
+ end
83
+ end
84
+
85
+ diffs
86
+ end
87
+
88
+ end
89
+
90
+ module ElementCleaner
91
+ # Recursively cleans all elements in place.
92
+ #
93
+ # Only allow tags in whitelist. Always parse the html with a parser and delete
94
+ # all tags that arent on the list.
95
+ #
96
+ # For feed elements that can contain HTML:
97
+ # - feed.(title|description)
98
+ # - feed.entries[n].(title|description|content)
99
+ #
100
+ def clean!
101
+ self.class::SIMPLE_ELEMENTS.each do |element|
102
+ val = self.send(element)
103
+
104
+ send("#{element}=", (val.is_a?(Array) ?
105
+ val.collect{|v| HtmlCleaner.flatten(v.to_s)} : HtmlCleaner.flatten(val.to_s)))
106
+ end
107
+
108
+ self.class::HTML_ELEMENTS.each do |element|
109
+ send("#{element}=", HtmlCleaner.clean(self.send(element).to_s))
110
+ end
111
+
112
+ self.class::BLENDED_ELEMENTS.each do |element|
113
+ self.send(element).collect{|v| v.clean!}
114
+ end
115
+ end
116
+ end
117
+
118
+ module TimeFix
119
+ # Reparse any Time instances, due to RSS::Parser's redefinition of
120
+ # certain aspects of the Time class that creates unexpected behaviour
121
+ # when extending the Time class, as some common third party libraries do.
122
+ # See http://code.google.com/p/feed-normalizer/issues/detail?id=13.
123
+ def reparse(obj)
124
+ @parsed ||= false
125
+
126
+ return obj if @parsed
127
+
128
+ if obj.is_a?(Time)
129
+ @parsed = true
130
+ Time.at(obj) rescue obj
131
+ end
132
+ end
133
+ end
134
+
135
+ module RewriteRelativeLinks
136
+ def rewrite_relative_links(text, url)
137
+ if host = url_host(url)
138
+ text.to_s.gsub(/(href|src)=('|")\//, '\1=\2http://' + host + '/')
139
+ else
140
+ text
141
+ end
142
+ end
143
+
144
+ private
145
+ def url_host(url)
146
+ URI.parse(url).host rescue nil
147
+ end
148
+ end
149
+
150
+
151
+ # Represents a feed item entry.
152
+ # Available fields are:
153
+ # * content
154
+ # * description
155
+ # * title
156
+ # * date_published
157
+ # * urls / url
158
+ # * id
159
+ # * authors / author
160
+ # * copyright
161
+ # * categories
162
+ class Entry
163
+ include Singular, ElementEquality, ElementCleaner, TimeFix, RewriteRelativeLinks
164
+
165
+ HTML_ELEMENTS = [:content, :description, :title]
166
+ SIMPLE_ELEMENTS = [:date_published, :urls, :id, :authors, :copyright, :categories, :last_updated, :enclosures]
167
+ BLENDED_ELEMENTS = []
168
+
169
+ ELEMENTS = HTML_ELEMENTS + SIMPLE_ELEMENTS + BLENDED_ELEMENTS
170
+
171
+ attr_accessor(*ELEMENTS)
172
+
173
+ def initialize
174
+ @urls = []
175
+ @authors = []
176
+ @categories = []
177
+ @enclosures = []
178
+ @date_published, @content = nil
179
+ end
180
+
181
+ undef date_published
182
+ def date_published
183
+ @date_published = reparse(@date_published)
184
+ end
185
+
186
+ undef content
187
+ def content
188
+ @content = rewrite_relative_links(@content, url)
189
+ end
190
+
191
+ end
192
+
193
+ # Represents the root element of a feed.
194
+ # Available fields are:
195
+ # * title
196
+ # * description
197
+ # * id
198
+ # * last_updated
199
+ # * copyright
200
+ # * authors / author
201
+ # * urls / url
202
+ # * image
203
+ # * generator
204
+ # * items / channel
205
+ class Feed
206
+ include Singular, ElementEquality, ElementCleaner, TimeFix
207
+
208
+ # Elements that can contain HTML fragments.
209
+ HTML_ELEMENTS = [:title, :description]
210
+
211
+ # Elements that contain 'plain' Strings, with HTML escaped.
212
+ SIMPLE_ELEMENTS = [:id, :last_updated, :copyright, :authors, :urls, :image, :generator, :ttl, :skip_hours, :skip_days]
213
+
214
+ # Elements that contain both HTML and escaped HTML.
215
+ BLENDED_ELEMENTS = [:items]
216
+
217
+ ELEMENTS = HTML_ELEMENTS + SIMPLE_ELEMENTS + BLENDED_ELEMENTS
218
+
219
+ attr_accessor(*ELEMENTS)
220
+ attr_accessor(:parser)
221
+
222
+ alias :entries :items
223
+
224
+ def initialize(wrapper)
225
+ # set up associations (i.e. arrays where needed)
226
+ @urls = []
227
+ @authors = []
228
+ @skip_hours = []
229
+ @skip_days = []
230
+ @items = []
231
+ @parser = wrapper.parser.to_s
232
+ @last_updated = nil
233
+ end
234
+
235
+ undef last_updated
236
+ def last_updated
237
+ @last_updated = reparse(@last_updated)
238
+ end
239
+
240
+ def channel() self end
241
+
242
+ end
243
+
244
+ end
245
+