feedtools 0.2.10 → 0.2.11
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/CHANGELOG +8 -0
- data/db/schema.mysql.sql +11 -10
- data/db/schema.postgresql.sql +10 -9
- data/db/schema.sqlite.sql +10 -9
- data/lib/feed_tools.rb +11 -3752
- data/lib/{database_feed_cache.rb → feed_tools/database_feed_cache.rb} +0 -0
- data/lib/feed_tools/feed.rb +2073 -0
- data/lib/feed_tools/feed_item.rb +1634 -0
- data/rakefile +1 -1
- metadata +5 -3
@@ -0,0 +1,1634 @@
|
|
1
|
+
module FeedTools
|
2
|
+
# The <tt>FeedTools::FeedItem</tt> class represents the structure of
|
3
|
+
# a single item within a web feed.
|
4
|
+
class FeedItem
|
5
|
+
include REXML
|
6
|
+
|
7
|
+
# This class stores information about a feed item's file enclosures.
|
8
|
+
class Enclosure
|
9
|
+
# The url for the enclosure
|
10
|
+
attr_accessor :url
|
11
|
+
# The MIME type of the file referenced by the enclosure
|
12
|
+
attr_accessor :type
|
13
|
+
# The size of the file referenced by the enclosure
|
14
|
+
attr_accessor :file_size
|
15
|
+
# The total play time of the file referenced by the enclosure
|
16
|
+
attr_accessor :duration
|
17
|
+
# The height in pixels of the enclosed media
|
18
|
+
attr_accessor :height
|
19
|
+
# The width in pixels of the enclosed media
|
20
|
+
attr_accessor :width
|
21
|
+
# The bitrate of the enclosed media
|
22
|
+
attr_accessor :bitrate
|
23
|
+
# The framerate of the enclosed media
|
24
|
+
attr_accessor :framerate
|
25
|
+
# The thumbnail for this enclosure
|
26
|
+
attr_accessor :thumbnail
|
27
|
+
# The categories for this enclosure
|
28
|
+
attr_accessor :categories
|
29
|
+
# A hash of the enclosed file
|
30
|
+
attr_accessor :hash
|
31
|
+
# A website containing some kind of media player instead of a direct
|
32
|
+
# link to the media file.
|
33
|
+
attr_accessor :player
|
34
|
+
# A list of credits for the enclosed media
|
35
|
+
attr_accessor :credits
|
36
|
+
# A text rendition of the enclosed media
|
37
|
+
attr_accessor :text
|
38
|
+
# A list of alternate version of the enclosed media file
|
39
|
+
attr_accessor :versions
|
40
|
+
# The default version of the enclosed media file
|
41
|
+
attr_accessor :default_version
|
42
|
+
|
43
|
+
# Returns true if this is the default enclosure
|
44
|
+
def is_default?
|
45
|
+
return @is_default
|
46
|
+
end
|
47
|
+
|
48
|
+
# Sets whether this is the default enclosure for the media group
|
49
|
+
def is_default=(new_is_default)
|
50
|
+
@is_default = new_is_default
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns true if the enclosure contains explicit material
|
54
|
+
def explicit?
|
55
|
+
return @explicit
|
56
|
+
end
|
57
|
+
|
58
|
+
# Sets the explicit attribute on the enclosure
|
59
|
+
def explicit=(new_explicit)
|
60
|
+
@explicit = new_explicit
|
61
|
+
end
|
62
|
+
|
63
|
+
# Determines if the object is a sample, or the full version of the
|
64
|
+
# object, or if it is a stream.
|
65
|
+
# Possible values are 'sample', 'full', 'nonstop'.
|
66
|
+
def expression
|
67
|
+
return @expression
|
68
|
+
end
|
69
|
+
|
70
|
+
# Sets the expression attribute on the enclosure.
|
71
|
+
# Allowed values are 'sample', 'full', 'nonstop'.
|
72
|
+
def expression=(new_expression)
|
73
|
+
unless ['sample', 'full', 'nonstop'].include? new_expression.downcase
|
74
|
+
raise ArgumentError,
|
75
|
+
"Permitted values are 'sample', 'full', 'nonstop'."
|
76
|
+
end
|
77
|
+
@expression = new_expression.downcase
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns true if this enclosure contains audio content
|
81
|
+
def audio?
|
82
|
+
unless self.type.nil?
|
83
|
+
return true if (self.type =~ /^audio/) != nil
|
84
|
+
end
|
85
|
+
# TODO: create a more complete list
|
86
|
+
# =================================
|
87
|
+
audio_extensions = ['mp3', 'm4a', 'm4p', 'wav', 'ogg', 'wma']
|
88
|
+
audio_extensions.each do |extension|
|
89
|
+
if (url =~ /#{extension}$/) != nil
|
90
|
+
return true
|
91
|
+
end
|
92
|
+
end
|
93
|
+
return false
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns true if this enclosure contains video content
|
97
|
+
def video?
|
98
|
+
unless self.type.nil?
|
99
|
+
return true if (self.type =~ /^video/) != nil
|
100
|
+
return true if self.type == "image/mov"
|
101
|
+
end
|
102
|
+
# TODO: create a more complete list
|
103
|
+
# =================================
|
104
|
+
video_extensions = ['mov', 'mp4', 'avi', 'wmv', 'asf']
|
105
|
+
video_extensions.each do |extension|
|
106
|
+
if (url =~ /#{extension}$/) != nil
|
107
|
+
return true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
return false
|
111
|
+
end
|
112
|
+
|
113
|
+
alias_method :link, :url
|
114
|
+
alias_method :link=, :url=
|
115
|
+
end
|
116
|
+
|
117
|
+
# TODO: Make these actual classes instead of structs
|
118
|
+
# ==================================================
|
119
|
+
EnclosureHash = Struct.new( "EnclosureHash", :hash, :type )
|
120
|
+
EnclosurePlayer = Struct.new( "EnclosurePlayer", :url, :height, :width )
|
121
|
+
EnclosureCredit = Struct.new( "EnclosureCredit", :name, :role )
|
122
|
+
EnclosureThumbnail = Struct.new( "EnclosureThumbnail", :url, :height,
|
123
|
+
:width )
|
124
|
+
|
125
|
+
# Initialize the feed object
|
126
|
+
def initialize
|
127
|
+
super
|
128
|
+
@feed = nil
|
129
|
+
@xml_doc = nil
|
130
|
+
@root_node = nil
|
131
|
+
@title = nil
|
132
|
+
@id = nil
|
133
|
+
@time = nil
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns the parent feed of this feed item
|
137
|
+
def feed
|
138
|
+
return @feed
|
139
|
+
end
|
140
|
+
|
141
|
+
# Sets the parent feed of this feed item
|
142
|
+
def feed=(new_feed)
|
143
|
+
@feed = new_feed
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns the feed item's raw xml data.
|
147
|
+
def xml_data
|
148
|
+
return @xml_data
|
149
|
+
end
|
150
|
+
|
151
|
+
# Sets the feed item's xml data.
|
152
|
+
def xml_data=(new_xml_data)
|
153
|
+
@xml_data = new_xml_data
|
154
|
+
end
|
155
|
+
|
156
|
+
# Returns a REXML Document of the xml_data
|
157
|
+
def xml
|
158
|
+
if @xml_doc.nil?
|
159
|
+
# TODO: :ignore_whitespace_nodes => :all
|
160
|
+
# Add that?
|
161
|
+
# ======================================
|
162
|
+
@xml_doc = Document.new(xml_data)
|
163
|
+
end
|
164
|
+
return @xml_doc
|
165
|
+
end
|
166
|
+
|
167
|
+
# Returns the first node within the root_node that matches the xpath query.
|
168
|
+
def find_node(xpath)
|
169
|
+
return XPath.first(root_node, xpath)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Returns all nodes within the root_node that match the xpath query.
|
173
|
+
def find_all_nodes(xpath)
|
174
|
+
return XPath.match(root_node, xpath)
|
175
|
+
end
|
176
|
+
|
177
|
+
# Returns the root node of the feed item.
|
178
|
+
def root_node
|
179
|
+
if @root_node.nil?
|
180
|
+
@root_node = xml.root
|
181
|
+
end
|
182
|
+
return @root_node
|
183
|
+
end
|
184
|
+
|
185
|
+
# Returns the feed items's unique id
|
186
|
+
def id
|
187
|
+
if @id.nil?
|
188
|
+
unless root_node.nil?
|
189
|
+
@id = XPath.first(root_node, "id/text()").to_s
|
190
|
+
if @id == ""
|
191
|
+
@id = XPath.first(root_node, "guid/text()").to_s
|
192
|
+
end
|
193
|
+
end
|
194
|
+
@id = nil if @id == ""
|
195
|
+
end
|
196
|
+
return @id
|
197
|
+
end
|
198
|
+
|
199
|
+
# Sets the feed item's unique id
|
200
|
+
def id=(new_id)
|
201
|
+
@id = new_id
|
202
|
+
end
|
203
|
+
|
204
|
+
# Returns the feed item title
|
205
|
+
def title
|
206
|
+
if @title.nil?
|
207
|
+
unless root_node.nil?
|
208
|
+
repair_entities = false
|
209
|
+
title_node = XPath.first(root_node, "title")
|
210
|
+
if title_node.nil?
|
211
|
+
title_node = XPath.first(root_node, "dc:title")
|
212
|
+
end
|
213
|
+
if title_node.nil?
|
214
|
+
title_node = XPath.first(root_node, "TITLE")
|
215
|
+
end
|
216
|
+
end
|
217
|
+
if title_node.nil?
|
218
|
+
return nil
|
219
|
+
end
|
220
|
+
if XPath.first(title_node, "@type").to_s == "xhtml" ||
|
221
|
+
XPath.first(title_node, "@mode").to_s == "xhtml" ||
|
222
|
+
XPath.first(title_node, "@type").to_s == "xml" ||
|
223
|
+
XPath.first(title_node, "@mode").to_s == "xml" ||
|
224
|
+
XPath.first(title_node, "@type").to_s == "application/xhtml+xml"
|
225
|
+
@title = title_node.inner_xml
|
226
|
+
elsif XPath.first(title_node, "@type").to_s == "escaped" ||
|
227
|
+
XPath.first(title_node, "@mode").to_s == "escaped"
|
228
|
+
@title = FeedTools.unescape_entities(
|
229
|
+
XPath.first(title_node, "text()").to_s)
|
230
|
+
else
|
231
|
+
@title = title_node.inner_xml
|
232
|
+
repair_entities = true
|
233
|
+
end
|
234
|
+
unless @title.nil?
|
235
|
+
@title = FeedTools.sanitize_html(@title, :strip)
|
236
|
+
@title = FeedTools.unescape_entities(@title) if repair_entities
|
237
|
+
@title = FeedTools.tidy_html(@title)
|
238
|
+
end
|
239
|
+
if @title != ""
|
240
|
+
# Some blogging tools include the number of comments in a post
|
241
|
+
# in the title... this is supremely ugly, and breaks any
|
242
|
+
# applications which expect the title to be static, so we're
|
243
|
+
# gonna strip them out.
|
244
|
+
#
|
245
|
+
# If for some incredibly wierd reason you need the actual
|
246
|
+
# unstripped title, just use find_node("title/text()").to_s
|
247
|
+
@title = @title.strip.gsub(/\[\d*\]$/, "").strip
|
248
|
+
end
|
249
|
+
@title.gsub!(/\n/, " ")
|
250
|
+
@title.strip!
|
251
|
+
@title = nil if @title == ""
|
252
|
+
end
|
253
|
+
return @title
|
254
|
+
end
|
255
|
+
|
256
|
+
# Sets the feed item title
|
257
|
+
def title=(new_title)
|
258
|
+
@title = new_title
|
259
|
+
end
|
260
|
+
|
261
|
+
# Returns the feed item description
|
262
|
+
def description
|
263
|
+
if @description.nil?
|
264
|
+
unless root_node.nil?
|
265
|
+
repair_entities = false
|
266
|
+
description_node = XPath.first(root_node, "content:encoded")
|
267
|
+
if description_node.nil?
|
268
|
+
description_node = XPath.first(root_node, "content")
|
269
|
+
end
|
270
|
+
if description_node.nil?
|
271
|
+
description_node = XPath.first(root_node, "fullitem")
|
272
|
+
end
|
273
|
+
if description_node.nil?
|
274
|
+
description_node = XPath.first(root_node, "xhtml:body")
|
275
|
+
end
|
276
|
+
if description_node.nil?
|
277
|
+
description_node = XPath.first(root_node, "body")
|
278
|
+
end
|
279
|
+
if description_node.nil?
|
280
|
+
description_node = XPath.first(root_node, "description")
|
281
|
+
end
|
282
|
+
if description_node.nil?
|
283
|
+
description_node = XPath.first(root_node, "tagline")
|
284
|
+
end
|
285
|
+
if description_node.nil?
|
286
|
+
description_node = XPath.first(root_node, "subtitle")
|
287
|
+
end
|
288
|
+
if description_node.nil?
|
289
|
+
description_node = XPath.first(root_node, "summary")
|
290
|
+
end
|
291
|
+
if description_node.nil?
|
292
|
+
description_node = XPath.first(root_node, "abstract")
|
293
|
+
end
|
294
|
+
if description_node.nil?
|
295
|
+
description_node = XPath.first(root_node, "ABSTRACT")
|
296
|
+
end
|
297
|
+
if description_node.nil?
|
298
|
+
description_node = XPath.first(root_node, "info")
|
299
|
+
@bozo = true unless description_node.nil?
|
300
|
+
end
|
301
|
+
end
|
302
|
+
if description_node.nil?
|
303
|
+
return nil
|
304
|
+
end
|
305
|
+
unless description_node.nil?
|
306
|
+
if XPath.first(description_node, "@encoding").to_s != ""
|
307
|
+
@description =
|
308
|
+
"[Embedded data objects are not currently supported.]"
|
309
|
+
elsif XPath.first(description_node, "@type").to_s == "xhtml" ||
|
310
|
+
XPath.first(description_node, "@mode").to_s == "xhtml" ||
|
311
|
+
XPath.first(description_node, "@type").to_s == "xml" ||
|
312
|
+
XPath.first(description_node, "@mode").to_s == "xml" ||
|
313
|
+
XPath.first(description_node, "@type").to_s ==
|
314
|
+
"application/xhtml+xml"
|
315
|
+
@description = description_node.inner_xml
|
316
|
+
elsif XPath.first(description_node, "@type").to_s == "escaped" ||
|
317
|
+
XPath.first(description_node, "@mode").to_s == "escaped"
|
318
|
+
@description = FeedTools.unescape_entities(
|
319
|
+
description_node.inner_xml)
|
320
|
+
else
|
321
|
+
@description = description_node.inner_xml
|
322
|
+
repair_entities = true
|
323
|
+
end
|
324
|
+
end
|
325
|
+
if @description == ""
|
326
|
+
@description = self.itunes_summary
|
327
|
+
@description = "" if @description.nil?
|
328
|
+
end
|
329
|
+
if @description == ""
|
330
|
+
@description = self.itunes_subtitle
|
331
|
+
@description = "" if @description.nil?
|
332
|
+
end
|
333
|
+
|
334
|
+
unless @description.nil?
|
335
|
+
@description = FeedTools.sanitize_html(@description, :strip)
|
336
|
+
@description = FeedTools.unescape_entities(@description) if repair_entities
|
337
|
+
@description = FeedTools.tidy_html(@description)
|
338
|
+
end
|
339
|
+
|
340
|
+
@description = @description.strip unless @description.nil?
|
341
|
+
@description = nil if @description == ""
|
342
|
+
end
|
343
|
+
return @description
|
344
|
+
end
|
345
|
+
|
346
|
+
# Sets the feed item description
|
347
|
+
def description=(new_description)
|
348
|
+
@description = new_description
|
349
|
+
end
|
350
|
+
|
351
|
+
# Returns the contents of the itunes:summary element
|
352
|
+
def itunes_summary
|
353
|
+
if @itunes_summary.nil?
|
354
|
+
@itunes_summary = FeedTools.unescape_entities(XPath.first(root_node,
|
355
|
+
"itunes:summary/text()").to_s)
|
356
|
+
if @itunes_summary == ""
|
357
|
+
@itunes_summary = nil
|
358
|
+
end
|
359
|
+
unless @itunes_summary.nil?
|
360
|
+
@itunes_summary = FeedTools.sanitize_html(@itunes_summary)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
return @itunes_summary
|
364
|
+
end
|
365
|
+
|
366
|
+
# Sets the contents of the itunes:summary element
|
367
|
+
def itunes_summary=(new_itunes_summary)
|
368
|
+
@itunes_summary = new_itunes_summary
|
369
|
+
end
|
370
|
+
|
371
|
+
# Returns the contents of the itunes:subtitle element
|
372
|
+
def itunes_subtitle
|
373
|
+
if @itunes_subtitle.nil?
|
374
|
+
@itunes_subtitle = FeedTools.unescape_entities(XPath.first(root_node,
|
375
|
+
"itunes:subtitle/text()").to_s)
|
376
|
+
if @itunes_subtitle == ""
|
377
|
+
@itunes_subtitle = nil
|
378
|
+
end
|
379
|
+
unless @itunes_subtitle.nil?
|
380
|
+
@itunes_subtitle = FeedTools.sanitize_html(@itunes_subtitle)
|
381
|
+
end
|
382
|
+
end
|
383
|
+
return @itunes_subtitle
|
384
|
+
end
|
385
|
+
|
386
|
+
# Sets the contents of the itunes:subtitle element
|
387
|
+
def itunes_subtitle=(new_itunes_subtitle)
|
388
|
+
@itunes_subtitle = new_itunes_subtitle
|
389
|
+
end
|
390
|
+
|
391
|
+
# Returns the contents of the media:text element
|
392
|
+
def media_text
|
393
|
+
if @media_text.nil?
|
394
|
+
@media_text = FeedTools.unescape_entities(XPath.first(root_node,
|
395
|
+
"itunes:subtitle/text()").to_s)
|
396
|
+
if @media_text == ""
|
397
|
+
@media_text = nil
|
398
|
+
end
|
399
|
+
unless @media_text.nil?
|
400
|
+
@media_text = FeedTools.sanitize_html(@media_text)
|
401
|
+
end
|
402
|
+
end
|
403
|
+
return @media_text
|
404
|
+
end
|
405
|
+
|
406
|
+
# Sets the contents of the media:text element
|
407
|
+
def media_text=(new_media_text)
|
408
|
+
@media_text = new_media_text
|
409
|
+
end
|
410
|
+
|
411
|
+
# Returns the feed item link
|
412
|
+
def link
|
413
|
+
if @link.nil?
|
414
|
+
unless root_node.nil?
|
415
|
+
@link = XPath.first(root_node, "link[@rel='alternate']/@href").to_s
|
416
|
+
if @link == ""
|
417
|
+
@link = XPath.first(root_node, "link/@href").to_s
|
418
|
+
end
|
419
|
+
if @link == ""
|
420
|
+
@link = XPath.first(root_node, "link/text()").to_s
|
421
|
+
end
|
422
|
+
if @link == ""
|
423
|
+
@link = XPath.first(root_node, "@rdf:about").to_s
|
424
|
+
end
|
425
|
+
if @link == ""
|
426
|
+
@link = XPath.first(root_node, "guid[@isPermaLink='true']/text()").to_s
|
427
|
+
end
|
428
|
+
if @link == ""
|
429
|
+
@link = XPath.first(root_node, "@href").to_s
|
430
|
+
end
|
431
|
+
if @link == ""
|
432
|
+
@link = XPath.first(root_node, "a/@href").to_s
|
433
|
+
end
|
434
|
+
if @link == ""
|
435
|
+
@link = XPath.first(root_node, "@HREF").to_s
|
436
|
+
end
|
437
|
+
if @link == ""
|
438
|
+
@link = XPath.first(root_node, "A/@HREF").to_s
|
439
|
+
end
|
440
|
+
end
|
441
|
+
if @link == "" || @link.nil?
|
442
|
+
if FeedTools.is_uri? self.guid
|
443
|
+
@link = self.guid
|
444
|
+
end
|
445
|
+
end
|
446
|
+
if @link != ""
|
447
|
+
@link = FeedTools.unescape_entities(@link)
|
448
|
+
end
|
449
|
+
# TODO: Actually implement proper relative url resolving instead of this crap
|
450
|
+
# ===========================================================================
|
451
|
+
#
|
452
|
+
# if @link != "" && (@link =~ /http:\/\//) != 0 && (@link =~ /https:\/\//) != 0
|
453
|
+
# if (feed.base[-1..-1] == "/" && @link[0..0] == "/")
|
454
|
+
# @link = @link[1..-1]
|
455
|
+
# end
|
456
|
+
# # prepend the base to the link since they seem to have used a relative path
|
457
|
+
# @link = feed.base + @link
|
458
|
+
# end
|
459
|
+
@link = FeedTools.normalize_url(@link)
|
460
|
+
end
|
461
|
+
return @link
|
462
|
+
end
|
463
|
+
|
464
|
+
# Sets the feed item link
|
465
|
+
def link=(new_link)
|
466
|
+
@link = new_link
|
467
|
+
end
|
468
|
+
|
469
|
+
# Returns a list of the feed item's categories
|
470
|
+
def categories
|
471
|
+
if @categories.nil?
|
472
|
+
@categories = []
|
473
|
+
category_nodes = XPath.match(root_node, "category")
|
474
|
+
if category_nodes.nil? || category_nodes.empty?
|
475
|
+
category_nodes = XPath.match(root_node, "dc:subject")
|
476
|
+
end
|
477
|
+
unless category_nodes.nil?
|
478
|
+
for category_node in category_nodes
|
479
|
+
category = FeedTools::Feed::Category.new
|
480
|
+
category.term = XPath.first(category_node, "@term").to_s
|
481
|
+
if category.term == ""
|
482
|
+
category.term = XPath.first(category_node, "text()").to_s
|
483
|
+
end
|
484
|
+
category.term.strip! unless category.term.nil?
|
485
|
+
category.term = nil if category.term == ""
|
486
|
+
category.label = XPath.first(category_node, "@label").to_s
|
487
|
+
category.label.strip! unless category.label.nil?
|
488
|
+
category.label = nil if category.label == ""
|
489
|
+
category.scheme = XPath.first(category_node, "@scheme").to_s
|
490
|
+
if category.scheme == ""
|
491
|
+
category.scheme = XPath.first(category_node, "@domain").to_s
|
492
|
+
end
|
493
|
+
category.scheme.strip! unless category.scheme.nil?
|
494
|
+
category.scheme = nil if category.scheme == ""
|
495
|
+
@categories << category
|
496
|
+
end
|
497
|
+
end
|
498
|
+
end
|
499
|
+
return @categories
|
500
|
+
end
|
501
|
+
|
502
|
+
# Returns a list of the feed items's images
|
503
|
+
def images
|
504
|
+
if @images.nil?
|
505
|
+
@images = []
|
506
|
+
image_nodes = XPath.match(root_node, "link")
|
507
|
+
if image_nodes.nil? || image_nodes.empty?
|
508
|
+
image_nodes = XPath.match(root_node, "logo")
|
509
|
+
end
|
510
|
+
if image_nodes.nil? || image_nodes.empty?
|
511
|
+
image_nodes = XPath.match(root_node, "LOGO")
|
512
|
+
end
|
513
|
+
if image_nodes.nil? || image_nodes.empty?
|
514
|
+
image_nodes = XPath.match(root_node, "image")
|
515
|
+
end
|
516
|
+
unless image_nodes.nil?
|
517
|
+
for image_node in image_nodes
|
518
|
+
image = FeedTools::Feed::Image.new
|
519
|
+
image.url = XPath.first(image_node, "url/text()").to_s
|
520
|
+
if image.url != ""
|
521
|
+
self.feed.bozo = true
|
522
|
+
end
|
523
|
+
if image.url == ""
|
524
|
+
image.url = XPath.first(image_node, "@rdf:resource").to_s
|
525
|
+
end
|
526
|
+
if image.url == "" && (image_node.name == "logo" ||
|
527
|
+
(image_node.attributes['type'] =~ /^image/) == 0)
|
528
|
+
image.url = XPath.first(image_node, "@href").to_s
|
529
|
+
end
|
530
|
+
if image.url == "" && image_node.name == "LOGO"
|
531
|
+
image.url = XPath.first(image_node, "@HREF").to_s
|
532
|
+
end
|
533
|
+
image.url.strip! unless image.url.nil?
|
534
|
+
image.url = nil if image.url == ""
|
535
|
+
image.title = XPath.first(image_node, "title/text()").to_s
|
536
|
+
image.title.strip! unless image.title.nil?
|
537
|
+
image.title = nil if image.title == ""
|
538
|
+
image.description =
|
539
|
+
XPath.first(image_node, "description/text()").to_s
|
540
|
+
image.description.strip! unless image.description.nil?
|
541
|
+
image.description = nil if image.description == ""
|
542
|
+
image.link = XPath.first(image_node, "link/text()").to_s
|
543
|
+
image.link.strip! unless image.link.nil?
|
544
|
+
image.link = nil if image.link == ""
|
545
|
+
image.height = XPath.first(image_node, "height/text()").to_s.to_i
|
546
|
+
image.height = nil if image.height <= 0
|
547
|
+
image.width = XPath.first(image_node, "width/text()").to_s.to_i
|
548
|
+
image.width = nil if image.width <= 0
|
549
|
+
image.style = XPath.first(image_node, "@style").to_s.downcase
|
550
|
+
if image.style == ""
|
551
|
+
image.style = XPath.first(image_node, "@STYLE").to_s.downcase
|
552
|
+
end
|
553
|
+
image.style.strip! unless image.style.nil?
|
554
|
+
image.style = nil if image.style == ""
|
555
|
+
@images << image
|
556
|
+
end
|
557
|
+
end
|
558
|
+
end
|
559
|
+
return @images
|
560
|
+
end
|
561
|
+
|
562
|
+
# Returns the feed item itunes image link
|
563
|
+
#
|
564
|
+
# If it's not present, falls back to the normal image link.
|
565
|
+
# Technically, the itunes spec says that the image needs to be
|
566
|
+
# square and larger than 300x300, but hey, if there's an image
|
567
|
+
# to be had, it's better than none at all.
|
568
|
+
def itunes_image_link
|
569
|
+
if @itunes_image_link.nil?
|
570
|
+
# get the feed item itunes image link from the xml document
|
571
|
+
@itunes_image_link = XPath.first(root_node, "itunes:image/@href").to_s
|
572
|
+
if @itunes_image_link == ""
|
573
|
+
@itunes_image_link = XPath.first(root_node, "itunes:link[@rel='image']/@href").to_s
|
574
|
+
end
|
575
|
+
@itunes_image_link = FeedTools.normalize_url(@itunes_image_link)
|
576
|
+
end
|
577
|
+
return @itunes_image_link
|
578
|
+
end
|
579
|
+
|
580
|
+
# Sets the feed item itunes image link
|
581
|
+
def itunes_image_link=(new_itunes_image_link)
|
582
|
+
@itunes_image_link = new_itunes_image_link
|
583
|
+
end
|
584
|
+
|
585
|
+
# Returns the feed item media thumbnail link
|
586
|
+
#
|
587
|
+
# If it's not present, falls back to the normal image link.
|
588
|
+
def media_thumbnail_link
|
589
|
+
if @media_thumbnail_link.nil?
|
590
|
+
# get the feed item itunes image link from the xml document
|
591
|
+
@media_thumbnail_link = XPath.first(root_node, "media:thumbnail/@url").to_s
|
592
|
+
@media_thumbnail_link = FeedTools.normalize_url(@media_thumbnail_link)
|
593
|
+
end
|
594
|
+
return @media_thumbnail_link
|
595
|
+
end
|
596
|
+
|
597
|
+
# Sets the feed item media thumbnail url
|
598
|
+
def media_thumbnail_link=(new_media_thumbnail_link)
|
599
|
+
@media_thumbnail_link = new_media_thumbnail_link
|
600
|
+
end
|
601
|
+
|
602
|
+
# Returns the feed item's copyright information
|
603
|
+
def copyright
|
604
|
+
if @copyright.nil?
|
605
|
+
unless root_node.nil?
|
606
|
+
@copyright = XPath.first(root_node, "dc:rights/text()").to_s
|
607
|
+
if @copyright == ""
|
608
|
+
@copyright = XPath.first(root_node, "rights/text()").to_s
|
609
|
+
end
|
610
|
+
if @copyright == ""
|
611
|
+
@copyright = XPath.first(root_node, "copyright/text()").to_s
|
612
|
+
end
|
613
|
+
if @copyright == ""
|
614
|
+
@copyright = XPath.first(root_node, "copyrights/text()").to_s
|
615
|
+
end
|
616
|
+
@copyright = FeedTools.sanitize_html(@copyright, :strip)
|
617
|
+
@copyright = nil if @copyright == ""
|
618
|
+
end
|
619
|
+
end
|
620
|
+
return @copyright
|
621
|
+
end
|
622
|
+
|
623
|
+
# Sets the feed item's copyright information
|
624
|
+
def copyright=(new_copyright)
|
625
|
+
@copyright = new_copyright
|
626
|
+
end
|
627
|
+
|
628
|
+
# Returns all feed item enclosures
|
629
|
+
def enclosures
|
630
|
+
if @enclosures.nil?
|
631
|
+
@enclosures = []
|
632
|
+
|
633
|
+
# First, load up all the different possible sources of enclosures
|
634
|
+
rss_enclosures = XPath.match(root_node, "enclosure")
|
635
|
+
atom_enclosures = XPath.match(root_node, "link[@rel='enclosure']")
|
636
|
+
media_content_enclosures = XPath.match(root_node, "media:content")
|
637
|
+
media_group_enclosures = XPath.match(root_node, "media:group")
|
638
|
+
|
639
|
+
# Parse RSS-type enclosures. Thanks to a few buggy enclosures implementations,
|
640
|
+
# sometimes these also manage to show up in atom files.
|
641
|
+
for enclosure_node in rss_enclosures
|
642
|
+
enclosure = Enclosure.new
|
643
|
+
enclosure.url = FeedTools.unescape_entities(enclosure_node.attributes["url"].to_s)
|
644
|
+
enclosure.type = enclosure_node.attributes["type"].to_s
|
645
|
+
enclosure.file_size = enclosure_node.attributes["length"].to_i
|
646
|
+
enclosure.credits = []
|
647
|
+
enclosure.explicit = false
|
648
|
+
@enclosures << enclosure
|
649
|
+
end
|
650
|
+
|
651
|
+
# Parse atom-type enclosures. If there are repeats of the same enclosure object,
|
652
|
+
# we merge the two together.
|
653
|
+
for enclosure_node in atom_enclosures
|
654
|
+
enclosure_url = FeedTools.unescape_entities(enclosure_node.attributes["href"].to_s)
|
655
|
+
enclosure = nil
|
656
|
+
new_enclosure = false
|
657
|
+
for existing_enclosure in @enclosures
|
658
|
+
if existing_enclosure.url == enclosure_url
|
659
|
+
enclosure = existing_enclosure
|
660
|
+
break
|
661
|
+
end
|
662
|
+
end
|
663
|
+
if enclosure.nil?
|
664
|
+
new_enclosure = true
|
665
|
+
enclosure = Enclosure.new
|
666
|
+
end
|
667
|
+
enclosure.url = enclosure_url
|
668
|
+
enclosure.type = enclosure_node.attributes["type"].to_s
|
669
|
+
enclosure.file_size = enclosure_node.attributes["length"].to_i
|
670
|
+
enclosure.credits = []
|
671
|
+
enclosure.explicit = false
|
672
|
+
if new_enclosure
|
673
|
+
@enclosures << enclosure
|
674
|
+
end
|
675
|
+
end
|
676
|
+
|
677
|
+
# Creates an anonymous method to parse content objects from the media module. We
|
678
|
+
# do this to avoid excessive duplication of code since we have to do identical
|
679
|
+
# processing for content objects within group objects.
|
680
|
+
parse_media_content = lambda do |media_content_nodes|
|
681
|
+
affected_enclosures = []
|
682
|
+
for enclosure_node in media_content_nodes
|
683
|
+
enclosure_url = FeedTools.unescape_entities(enclosure_node.attributes["url"].to_s)
|
684
|
+
enclosure = nil
|
685
|
+
new_enclosure = false
|
686
|
+
for existing_enclosure in @enclosures
|
687
|
+
if existing_enclosure.url == enclosure_url
|
688
|
+
enclosure = existing_enclosure
|
689
|
+
break
|
690
|
+
end
|
691
|
+
end
|
692
|
+
if enclosure.nil?
|
693
|
+
new_enclosure = true
|
694
|
+
enclosure = Enclosure.new
|
695
|
+
end
|
696
|
+
enclosure.url = enclosure_url
|
697
|
+
enclosure.type = enclosure_node.attributes["type"].to_s
|
698
|
+
enclosure.file_size = enclosure_node.attributes["fileSize"].to_i
|
699
|
+
enclosure.duration = enclosure_node.attributes["duration"].to_s
|
700
|
+
enclosure.height = enclosure_node.attributes["height"].to_i
|
701
|
+
enclosure.width = enclosure_node.attributes["width"].to_i
|
702
|
+
enclosure.bitrate = enclosure_node.attributes["bitrate"].to_i
|
703
|
+
enclosure.framerate = enclosure_node.attributes["framerate"].to_i
|
704
|
+
enclosure.expression = enclosure_node.attributes["expression"].to_s
|
705
|
+
enclosure.is_default =
|
706
|
+
(enclosure_node.attributes["isDefault"].to_s.downcase == "true")
|
707
|
+
if XPath.first(enclosure_node, "media:thumbnail/@url").to_s != ""
|
708
|
+
enclosure.thumbnail = EnclosureThumbnail.new(
|
709
|
+
FeedTools.unescape_entities(XPath.first(enclosure_node, "media:thumbnail/@url").to_s),
|
710
|
+
FeedTools.unescape_entities(XPath.first(enclosure_node, "media:thumbnail/@height").to_s),
|
711
|
+
FeedTools.unescape_entities(XPath.first(enclosure_node, "media:thumbnail/@width").to_s)
|
712
|
+
)
|
713
|
+
if enclosure.thumbnail.height == ""
|
714
|
+
enclosure.thumbnail.height = nil
|
715
|
+
end
|
716
|
+
if enclosure.thumbnail.width == ""
|
717
|
+
enclosure.thumbnail.width = nil
|
718
|
+
end
|
719
|
+
end
|
720
|
+
enclosure.categories = []
|
721
|
+
for category in XPath.match(enclosure_node, "media:category")
|
722
|
+
enclosure.categories << FeedTools::Feed::Category.new
|
723
|
+
enclosure.categories.last.term =
|
724
|
+
FeedTools.unescape_entities(category.text)
|
725
|
+
enclosure.categories.last.scheme =
|
726
|
+
FeedTools.unescape_entities(category.attributes["scheme"].to_s)
|
727
|
+
enclosure.categories.last.label =
|
728
|
+
FeedTools.unescape_entities(category.attributes["label"].to_s)
|
729
|
+
if enclosure.categories.last.scheme == ""
|
730
|
+
enclosure.categories.last.scheme = nil
|
731
|
+
end
|
732
|
+
if enclosure.categories.last.label == ""
|
733
|
+
enclosure.categories.last.label = nil
|
734
|
+
end
|
735
|
+
end
|
736
|
+
if XPath.first(enclosure_node, "media:hash/text()").to_s != ""
|
737
|
+
enclosure.hash = EnclosureHash.new(
|
738
|
+
FeedTools.sanitize_html(FeedTools.unescape_entities(XPath.first(
|
739
|
+
enclosure_node, "media:hash/text()").to_s), :strip),
|
740
|
+
"md5"
|
741
|
+
)
|
742
|
+
end
|
743
|
+
if XPath.first(enclosure_node, "media:player/@url").to_s != ""
|
744
|
+
enclosure.player = EnclosurePlayer.new(
|
745
|
+
FeedTools.unescape_entities(XPath.first(enclosure_node, "media:player/@url").to_s),
|
746
|
+
FeedTools.unescape_entities(XPath.first(enclosure_node, "media:player/@height").to_s),
|
747
|
+
FeedTools.unescape_entities(XPath.first(enclosure_node, "media:player/@width").to_s)
|
748
|
+
)
|
749
|
+
if enclosure.player.height == ""
|
750
|
+
enclosure.player.height = nil
|
751
|
+
end
|
752
|
+
if enclosure.player.width == ""
|
753
|
+
enclosure.player.width = nil
|
754
|
+
end
|
755
|
+
end
|
756
|
+
enclosure.credits = []
|
757
|
+
for credit in XPath.match(enclosure_node, "media:credit")
|
758
|
+
enclosure.credits << EnclosureCredit.new(
|
759
|
+
FeedTools.unescape_entities(credit.text),
|
760
|
+
FeedTools.unescape_entities(credit.attributes["role"].to_s.downcase)
|
761
|
+
)
|
762
|
+
if enclosure.credits.last.role == ""
|
763
|
+
enclosure.credits.last.role = nil
|
764
|
+
end
|
765
|
+
end
|
766
|
+
enclosure.explicit = (XPath.first(enclosure_node,
|
767
|
+
"media:adult/text()").to_s.downcase == "true")
|
768
|
+
if XPath.first(enclosure_node, "media:text/text()").to_s != ""
|
769
|
+
enclosure.text = FeedTools.unescape_entities(XPath.first(enclosure_node,
|
770
|
+
"media:text/text()").to_s)
|
771
|
+
end
|
772
|
+
affected_enclosures << enclosure
|
773
|
+
if new_enclosure
|
774
|
+
@enclosures << enclosure
|
775
|
+
end
|
776
|
+
end
|
777
|
+
affected_enclosures
|
778
|
+
end
|
779
|
+
|
780
|
+
# Parse the independant content objects.
|
781
|
+
parse_media_content.call(media_content_enclosures)
|
782
|
+
|
783
|
+
media_groups = []
|
784
|
+
|
785
|
+
# Parse the group objects.
|
786
|
+
for media_group in media_group_enclosures
|
787
|
+
group_media_content_enclosures =
|
788
|
+
XPath.match(media_group, "media:content")
|
789
|
+
|
790
|
+
# Parse the content objects within the group objects.
|
791
|
+
affected_enclosures =
|
792
|
+
parse_media_content.call(group_media_content_enclosures)
|
793
|
+
|
794
|
+
# Now make sure that content objects inherit certain properties from
|
795
|
+
# the group objects.
|
796
|
+
for enclosure in affected_enclosures
|
797
|
+
if enclosure.thumbnail.nil? &&
|
798
|
+
XPath.first(media_group, "media:thumbnail/@url").to_s != ""
|
799
|
+
enclosure.thumbnail = EnclosureThumbnail.new(
|
800
|
+
FeedTools.unescape_entities(
|
801
|
+
XPath.first(media_group, "media:thumbnail/@url").to_s),
|
802
|
+
FeedTools.unescape_entities(
|
803
|
+
XPath.first(media_group, "media:thumbnail/@height").to_s),
|
804
|
+
FeedTools.unescape_entities(
|
805
|
+
XPath.first(media_group, "media:thumbnail/@width").to_s)
|
806
|
+
)
|
807
|
+
if enclosure.thumbnail.height == ""
|
808
|
+
enclosure.thumbnail.height = nil
|
809
|
+
end
|
810
|
+
if enclosure.thumbnail.width == ""
|
811
|
+
enclosure.thumbnail.width = nil
|
812
|
+
end
|
813
|
+
end
|
814
|
+
if (enclosure.categories.nil? || enclosure.categories.size == 0)
|
815
|
+
enclosure.categories = []
|
816
|
+
for category in XPath.match(media_group, "media:category")
|
817
|
+
enclosure.categories << FeedTools::Feed::Category.new
|
818
|
+
enclosure.categories.last.term =
|
819
|
+
FeedTools.unescape_entities(category.text)
|
820
|
+
enclosure.categories.last.scheme =
|
821
|
+
FeedTools.unescape_entities(category.attributes["scheme"].to_s)
|
822
|
+
enclosure.categories.last.label =
|
823
|
+
FeedTools.unescape_entities(category.attributes["label"].to_s)
|
824
|
+
if enclosure.categories.last.scheme == ""
|
825
|
+
enclosure.categories.last.scheme = nil
|
826
|
+
end
|
827
|
+
if enclosure.categories.last.label == ""
|
828
|
+
enclosure.categories.last.label = nil
|
829
|
+
end
|
830
|
+
end
|
831
|
+
end
|
832
|
+
if enclosure.hash.nil? &&
|
833
|
+
XPath.first(media_group, "media:hash/text()").to_s != ""
|
834
|
+
enclosure.hash = EnclosureHash.new(
|
835
|
+
FeedTools.unescape_entities(XPath.first(media_group, "media:hash/text()").to_s),
|
836
|
+
"md5"
|
837
|
+
)
|
838
|
+
end
|
839
|
+
if enclosure.player.nil? &&
|
840
|
+
XPath.first(media_group, "media:player/@url").to_s != ""
|
841
|
+
enclosure.player = EnclosurePlayer.new(
|
842
|
+
FeedTools.unescape_entities(XPath.first(media_group, "media:player/@url").to_s),
|
843
|
+
FeedTools.unescape_entities(XPath.first(media_group, "media:player/@height").to_s),
|
844
|
+
FeedTools.unescape_entities(XPath.first(media_group, "media:player/@width").to_s)
|
845
|
+
)
|
846
|
+
if enclosure.player.height == ""
|
847
|
+
enclosure.player.height = nil
|
848
|
+
end
|
849
|
+
if enclosure.player.width == ""
|
850
|
+
enclosure.player.width = nil
|
851
|
+
end
|
852
|
+
end
|
853
|
+
if enclosure.credits.nil? || enclosure.credits.size == 0
|
854
|
+
enclosure.credits = []
|
855
|
+
for credit in XPath.match(media_group, "media:credit")
|
856
|
+
enclosure.credits << EnclosureCredit.new(
|
857
|
+
FeedTools.unescape_entities(credit.text),
|
858
|
+
FeedTools.unescape_entities(credit.attributes["role"].to_s.downcase)
|
859
|
+
)
|
860
|
+
if enclosure.credits.last.role == ""
|
861
|
+
enclosure.credits.last.role = nil
|
862
|
+
end
|
863
|
+
end
|
864
|
+
end
|
865
|
+
if enclosure.explicit?.nil?
|
866
|
+
enclosure.explicit = (XPath.first(media_group,
|
867
|
+
"media:adult/text()").to_s.downcase == "true") ? true : false
|
868
|
+
end
|
869
|
+
if enclosure.text.nil? &&
|
870
|
+
XPath.first(media_group, "media:text/text()").to_s != ""
|
871
|
+
enclosure.text = FeedTools.sanitize_html(FeedTools.unescape_entities(
|
872
|
+
XPath.first(media_group, "media:text/text()").to_s), :strip)
|
873
|
+
end
|
874
|
+
end
|
875
|
+
|
876
|
+
# Keep track of the media groups
|
877
|
+
media_groups << affected_enclosures
|
878
|
+
end
|
879
|
+
|
880
|
+
# Now we need to inherit any relevant item level information.
|
881
|
+
if self.explicit?
|
882
|
+
for enclosure in @enclosures
|
883
|
+
enclosure.explicit = true
|
884
|
+
end
|
885
|
+
end
|
886
|
+
|
887
|
+
# Add all the itunes categories
|
888
|
+
for itunes_category in XPath.match(root_node, "itunes:category")
|
889
|
+
genre = "Podcasts"
|
890
|
+
category = itunes_category.attributes["text"].to_s
|
891
|
+
subcategory = XPath.first(itunes_category, "itunes:category/@text").to_s
|
892
|
+
category_path = genre
|
893
|
+
if category != ""
|
894
|
+
category_path << "/" + category
|
895
|
+
end
|
896
|
+
if subcategory != ""
|
897
|
+
category_path << "/" + subcategory
|
898
|
+
end
|
899
|
+
for enclosure in @enclosures
|
900
|
+
if enclosure.categories.nil?
|
901
|
+
enclosure.categories = []
|
902
|
+
end
|
903
|
+
enclosure.categories << EnclosureCategory.new(
|
904
|
+
FeedTools.unescape_entities(category_path),
|
905
|
+
FeedTools.unescape_entities("http://www.apple.com/itunes/store/"),
|
906
|
+
FeedTools.unescape_entities("iTunes Music Store Categories")
|
907
|
+
)
|
908
|
+
end
|
909
|
+
end
|
910
|
+
|
911
|
+
for enclosure in @enclosures
|
912
|
+
# Clean up any of those attributes that incorrectly have ""
|
913
|
+
# or 0 as their values
|
914
|
+
if enclosure.type == ""
|
915
|
+
enclosure.type = nil
|
916
|
+
end
|
917
|
+
if enclosure.file_size == 0
|
918
|
+
enclosure.file_size = nil
|
919
|
+
end
|
920
|
+
if enclosure.duration == 0
|
921
|
+
enclosure.duration = nil
|
922
|
+
end
|
923
|
+
if enclosure.height == 0
|
924
|
+
enclosure.height = nil
|
925
|
+
end
|
926
|
+
if enclosure.width == 0
|
927
|
+
enclosure.width = nil
|
928
|
+
end
|
929
|
+
if enclosure.bitrate == 0
|
930
|
+
enclosure.bitrate = nil
|
931
|
+
end
|
932
|
+
if enclosure.framerate == 0
|
933
|
+
enclosure.framerate = nil
|
934
|
+
end
|
935
|
+
if enclosure.expression == "" || enclosure.expression.nil?
|
936
|
+
enclosure.expression = "full"
|
937
|
+
end
|
938
|
+
|
939
|
+
# If an enclosure is missing the text field, fall back on the itunes:summary field
|
940
|
+
if enclosure.text.nil? || enclosure.text = ""
|
941
|
+
enclosure.text = self.itunes_summary
|
942
|
+
end
|
943
|
+
|
944
|
+
# Make sure we don't have duplicate categories
|
945
|
+
unless enclosure.categories.nil?
|
946
|
+
enclosure.categories.uniq!
|
947
|
+
end
|
948
|
+
end
|
949
|
+
|
950
|
+
# And finally, now things get complicated. This is where we make
|
951
|
+
# sure that the enclosures method only returns either default
|
952
|
+
# enclosures or enclosures with only one version. Any enclosures
|
953
|
+
# that are wrapped in a media:group will be placed in the appropriate
|
954
|
+
# versions field.
|
955
|
+
affected_enclosure_urls = []
|
956
|
+
for media_group in media_groups
|
957
|
+
affected_enclosure_urls =
|
958
|
+
affected_enclosure_urls | (media_group.map do |enclosure|
|
959
|
+
enclosure.url
|
960
|
+
end)
|
961
|
+
end
|
962
|
+
@enclosures.delete_if do |enclosure|
|
963
|
+
(affected_enclosure_urls.include? enclosure.url)
|
964
|
+
end
|
965
|
+
for media_group in media_groups
|
966
|
+
default_enclosure = nil
|
967
|
+
for enclosure in media_group
|
968
|
+
if enclosure.is_default?
|
969
|
+
default_enclosure = enclosure
|
970
|
+
end
|
971
|
+
end
|
972
|
+
for enclosure in media_group
|
973
|
+
enclosure.default_version = default_enclosure
|
974
|
+
enclosure.versions = media_group.clone
|
975
|
+
enclosure.versions.delete(enclosure)
|
976
|
+
end
|
977
|
+
@enclosures << default_enclosure
|
978
|
+
end
|
979
|
+
end
|
980
|
+
|
981
|
+
# If we have a single enclosure, it's safe to inherit the itunes:duration field
|
982
|
+
# if it's missing.
|
983
|
+
if @enclosures.size == 1
|
984
|
+
if @enclosures.first.duration.nil? || @enclosures.first.duration == 0
|
985
|
+
@enclosures.first.duration = self.itunes_duration
|
986
|
+
end
|
987
|
+
end
|
988
|
+
|
989
|
+
return @enclosures
|
990
|
+
end
|
991
|
+
|
992
|
+
def enclosures=(new_enclosures)
|
993
|
+
@enclosures = new_enclosures
|
994
|
+
end
|
995
|
+
|
996
|
+
# Returns the feed item author
|
997
|
+
def author
|
998
|
+
if @author.nil?
|
999
|
+
@author = FeedTools::Feed::Author.new
|
1000
|
+
unless root_node.nil?
|
1001
|
+
author_node = XPath.first(root_node, "author")
|
1002
|
+
if author_node.nil?
|
1003
|
+
author_node = XPath.first(root_node, "managingEditor")
|
1004
|
+
end
|
1005
|
+
if author_node.nil?
|
1006
|
+
author_node = XPath.first(root_node, "dc:author")
|
1007
|
+
end
|
1008
|
+
if author_node.nil?
|
1009
|
+
author_node = XPath.first(root_node, "dc:creator")
|
1010
|
+
end
|
1011
|
+
if author_node.nil?
|
1012
|
+
author_node = XPath.first(root_node, "atom:author")
|
1013
|
+
end
|
1014
|
+
end
|
1015
|
+
unless author_node.nil?
|
1016
|
+
@author.raw = FeedTools.unescape_entities(
|
1017
|
+
XPath.first(author_node, "text()").to_s)
|
1018
|
+
@author.raw = nil if @author.raw == ""
|
1019
|
+
unless @author.raw.nil?
|
1020
|
+
raw_scan = @author.raw.scan(
|
1021
|
+
/(.*)\((\b[A-Z0-9._%-\+]+@[A-Z0-9._%-]+\.[A-Z]{2,4}\b)\)/i)
|
1022
|
+
if raw_scan.nil? || raw_scan.size == 0
|
1023
|
+
raw_scan = @author.raw.scan(
|
1024
|
+
/(\b[A-Z0-9._%-\+]+@[A-Z0-9._%-]+\.[A-Z]{2,4}\b)\s*\((.*)\)/i)
|
1025
|
+
author_raw_pair = raw_scan.first.reverse unless raw_scan.size == 0
|
1026
|
+
else
|
1027
|
+
author_raw_pair = raw_scan.first
|
1028
|
+
end
|
1029
|
+
if raw_scan.nil? || raw_scan.size == 0
|
1030
|
+
email_scan = @author.raw.scan(
|
1031
|
+
/\b[A-Z0-9._%-\+]+@[A-Z0-9._%-]+\.[A-Z]{2,4}\b/i)
|
1032
|
+
if email_scan != nil && email_scan.size > 0
|
1033
|
+
@author.email = email_scan.first.strip
|
1034
|
+
end
|
1035
|
+
end
|
1036
|
+
unless author_raw_pair.nil? || author_raw_pair.size == 0
|
1037
|
+
@author.name = author_raw_pair.first.strip
|
1038
|
+
@author.email = author_raw_pair.last.strip
|
1039
|
+
else
|
1040
|
+
unless @author.raw.include?("@")
|
1041
|
+
# We can be reasonably sure we are looking at something
|
1042
|
+
# that the creator didn't intend to contain an email address if
|
1043
|
+
# it got through the preceeding regexes and it doesn't
|
1044
|
+
# contain the tell-tale '@' symbol.
|
1045
|
+
@author.name = @author.raw
|
1046
|
+
end
|
1047
|
+
end
|
1048
|
+
end
|
1049
|
+
@author.name = "" if @author.name.nil?
|
1050
|
+
if @author.name == ""
|
1051
|
+
@author.name = FeedTools.unescape_entities(
|
1052
|
+
XPath.first(author_node, "name/text()").to_s)
|
1053
|
+
end
|
1054
|
+
if @author.name == ""
|
1055
|
+
@author.name = FeedTools.unescape_entities(
|
1056
|
+
XPath.first(author_node, "@name").to_s)
|
1057
|
+
end
|
1058
|
+
if @author.email == ""
|
1059
|
+
@author.email = FeedTools.unescape_entities(
|
1060
|
+
XPath.first(author_node, "email/text()").to_s)
|
1061
|
+
end
|
1062
|
+
if @author.email == ""
|
1063
|
+
@author.email = FeedTools.unescape_entities(
|
1064
|
+
XPath.first(author_node, "@email").to_s)
|
1065
|
+
end
|
1066
|
+
if @author.url == ""
|
1067
|
+
@author.url = FeedTools.unescape_entities(
|
1068
|
+
XPath.first(author_node, "url/text()").to_s)
|
1069
|
+
end
|
1070
|
+
if @author.url == ""
|
1071
|
+
@author.url = FeedTools.unescape_entities(
|
1072
|
+
XPath.first(author_node, "@url").to_s)
|
1073
|
+
end
|
1074
|
+
@author.name = nil if @author.name == ""
|
1075
|
+
@author.raw = nil if @author.raw == ""
|
1076
|
+
@author.email = nil if @author.email == ""
|
1077
|
+
@author.url = nil if @author.url == ""
|
1078
|
+
end
|
1079
|
+
# Fallback on the itunes module if we didn't find an author name
|
1080
|
+
begin
|
1081
|
+
@author.name = self.itunes_author if @author.name.nil?
|
1082
|
+
rescue
|
1083
|
+
@author.name = nil
|
1084
|
+
end
|
1085
|
+
end
|
1086
|
+
return @author
|
1087
|
+
end
|
1088
|
+
|
1089
|
+
# Sets the feed item author
|
1090
|
+
def author=(new_author)
|
1091
|
+
if new_author.respond_to?(:name) &&
|
1092
|
+
new_author.respond_to?(:email) &&
|
1093
|
+
new_author.respond_to?(:url)
|
1094
|
+
# It's a complete author object, just set it.
|
1095
|
+
@author = new_author
|
1096
|
+
else
|
1097
|
+
# We're not looking at an author object, this is probably a string,
|
1098
|
+
# default to setting the author's name.
|
1099
|
+
if @author.nil?
|
1100
|
+
@author = FeedTools::Feed::Author.new
|
1101
|
+
end
|
1102
|
+
@author.name = new_author
|
1103
|
+
end
|
1104
|
+
end
|
1105
|
+
|
1106
|
+
# Returns the feed publisher
|
1107
|
+
def publisher
|
1108
|
+
if @publisher.nil?
|
1109
|
+
@publisher = FeedTools::Feed::Author.new
|
1110
|
+
|
1111
|
+
# Set the author name
|
1112
|
+
@publisher.raw = FeedTools.unescape_entities(
|
1113
|
+
XPath.first(root_node, "dc:publisher/text()").to_s)
|
1114
|
+
if @publisher.raw == ""
|
1115
|
+
@publisher.raw = FeedTools.unescape_entities(
|
1116
|
+
XPath.first(root_node, "webMaster/text()").to_s)
|
1117
|
+
end
|
1118
|
+
unless @publisher.raw == ""
|
1119
|
+
raw_scan = @publisher.raw.scan(
|
1120
|
+
/(.*)\((\b[A-Z0-9._%-\+]+@[A-Z0-9._%-]+\.[A-Z]{2,4}\b)\)/i)
|
1121
|
+
if raw_scan.nil? || raw_scan.size == 0
|
1122
|
+
raw_scan = @publisher.raw.scan(
|
1123
|
+
/(\b[A-Z0-9._%-\+]+@[A-Z0-9._%-]+\.[A-Z]{2,4}\b)\s*\((.*)\)/i)
|
1124
|
+
unless raw_scan.size == 0
|
1125
|
+
publisher_raw_pair = raw_scan.first.reverse
|
1126
|
+
end
|
1127
|
+
else
|
1128
|
+
publisher_raw_pair = raw_scan.first
|
1129
|
+
end
|
1130
|
+
if raw_scan.nil? || raw_scan.size == 0
|
1131
|
+
email_scan = @publisher.raw.scan(
|
1132
|
+
/\b[A-Z0-9._%-\+]+@[A-Z0-9._%-]+\.[A-Z]{2,4}\b/i)
|
1133
|
+
if email_scan != nil && email_scan.size > 0
|
1134
|
+
@publisher.email = email_scan.first.strip
|
1135
|
+
end
|
1136
|
+
end
|
1137
|
+
unless publisher_raw_pair.nil? || publisher_raw_pair.size == 0
|
1138
|
+
@publisher.name = publisher_raw_pair.first.strip
|
1139
|
+
@publisher.email = publisher_raw_pair.last.strip
|
1140
|
+
else
|
1141
|
+
unless @publisher.raw.include?("@")
|
1142
|
+
# We can be reasonably sure we are looking at something
|
1143
|
+
# that the creator didn't intend to contain an email address if
|
1144
|
+
# it got through the preceeding regexes and it doesn't
|
1145
|
+
# contain the tell-tale '@' symbol.
|
1146
|
+
@publisher.name = @publisher.raw
|
1147
|
+
end
|
1148
|
+
end
|
1149
|
+
end
|
1150
|
+
|
1151
|
+
@publisher.name = nil if @publisher.name == ""
|
1152
|
+
@publisher.raw = nil if @publisher.raw == ""
|
1153
|
+
@publisher.email = nil if @publisher.email == ""
|
1154
|
+
@publisher.url = nil if @publisher.url == ""
|
1155
|
+
end
|
1156
|
+
return @publisher
|
1157
|
+
end
|
1158
|
+
|
1159
|
+
# Sets the feed publisher
|
1160
|
+
def publisher=(new_publisher)
|
1161
|
+
if new_publisher.respond_to?(:name) &&
|
1162
|
+
new_publisher.respond_to?(:email) &&
|
1163
|
+
new_publisher.respond_to?(:url)
|
1164
|
+
# It's a complete Author object, just set it.
|
1165
|
+
@publisher = new_publisher
|
1166
|
+
else
|
1167
|
+
# We're not looking at an Author object, this is probably a string,
|
1168
|
+
# default to setting the publisher's name.
|
1169
|
+
if @publisher.nil?
|
1170
|
+
@publisher = FeedTools::Feed::Author.new
|
1171
|
+
end
|
1172
|
+
@publisher.name = new_publisher
|
1173
|
+
end
|
1174
|
+
end
|
1175
|
+
|
1176
|
+
# Returns the contents of the itunes:author element
|
1177
|
+
#
|
1178
|
+
# This inherits from any incorrectly placed channel-level itunes:author
|
1179
|
+
# elements. They're actually amazingly common. People don't read specs.
|
1180
|
+
def itunes_author
|
1181
|
+
if @itunes_author.nil?
|
1182
|
+
@itunes_author = FeedTools.unescape_entities(XPath.first(root_node,
|
1183
|
+
"itunes:author/text()").to_s)
|
1184
|
+
@itunes_author = feed.itunes_author if @itunes_author == ""
|
1185
|
+
@itunes_author = nil if @itunes_author == ""
|
1186
|
+
end
|
1187
|
+
return @itunes_author
|
1188
|
+
end
|
1189
|
+
|
1190
|
+
# Sets the contents of the itunes:author element
|
1191
|
+
def itunes_author=(new_itunes_author)
|
1192
|
+
@itunes_author = new_itunes_author
|
1193
|
+
end
|
1194
|
+
|
1195
|
+
# Returns the number of seconds that the associated media runs for
|
1196
|
+
def itunes_duration
|
1197
|
+
if @itunes_duration.nil?
|
1198
|
+
raw_duration = FeedTools.unescape_entities(XPath.first(root_node,
|
1199
|
+
"itunes:duration/text()").to_s)
|
1200
|
+
if raw_duration != ""
|
1201
|
+
hms = raw_duration.split(":").map { |x| x.to_i }
|
1202
|
+
if hms.size == 3
|
1203
|
+
@itunes_duration = hms[0].hour + hms[1].minute + hms[2]
|
1204
|
+
elsif hms.size == 2
|
1205
|
+
@itunes_duration = hms[0].minute + hms[1]
|
1206
|
+
elsif hms.size == 1
|
1207
|
+
@itunes_duration = hms[0]
|
1208
|
+
end
|
1209
|
+
end
|
1210
|
+
end
|
1211
|
+
return @itunes_duration
|
1212
|
+
end
|
1213
|
+
|
1214
|
+
# Sets the number of seconds that the associate media runs for
|
1215
|
+
def itunes_duration=(new_itunes_duration)
|
1216
|
+
@itunes_duration = new_itunes_duration
|
1217
|
+
end
|
1218
|
+
|
1219
|
+
# Returns the feed item time
|
1220
|
+
def time
|
1221
|
+
if @time.nil?
|
1222
|
+
unless root_node.nil?
|
1223
|
+
time_string = XPath.first(root_node, "pubDate/text()").to_s
|
1224
|
+
if time_string == ""
|
1225
|
+
time_string = XPath.first(root_node, "dc:date/text()").to_s
|
1226
|
+
end
|
1227
|
+
if time_string == ""
|
1228
|
+
time_string = XPath.first(root_node, "issued/text()").to_s
|
1229
|
+
end
|
1230
|
+
if time_string == ""
|
1231
|
+
time_string = XPath.first(root_node, "updated/text()").to_s
|
1232
|
+
end
|
1233
|
+
if time_string == ""
|
1234
|
+
time_string = XPath.first(root_node, "time/text()").to_s
|
1235
|
+
end
|
1236
|
+
end
|
1237
|
+
if time_string != nil && time_string != ""
|
1238
|
+
@time = Time.parse(time_string) rescue Time.now
|
1239
|
+
elsif time_string == nil
|
1240
|
+
@time = Time.now
|
1241
|
+
end
|
1242
|
+
end
|
1243
|
+
return @time
|
1244
|
+
end
|
1245
|
+
|
1246
|
+
# Sets the feed item time
|
1247
|
+
def time=(new_time)
|
1248
|
+
@time = new_time
|
1249
|
+
end
|
1250
|
+
|
1251
|
+
# Returns the feed item updated time
|
1252
|
+
def updated
|
1253
|
+
if @updated.nil?
|
1254
|
+
unless root_node.nil?
|
1255
|
+
updated_string = XPath.first(root_node, "updated/text()").to_s
|
1256
|
+
if updated_string == ""
|
1257
|
+
updated_string = XPath.first(root_node, "modified/text()").to_s
|
1258
|
+
end
|
1259
|
+
end
|
1260
|
+
if updated_string != nil && updated_string != ""
|
1261
|
+
@updated = Time.parse(updated_string) rescue nil
|
1262
|
+
else
|
1263
|
+
@updated = nil
|
1264
|
+
end
|
1265
|
+
end
|
1266
|
+
return @updated
|
1267
|
+
end
|
1268
|
+
|
1269
|
+
# Sets the feed item updated time
|
1270
|
+
def updated=(new_updated)
|
1271
|
+
@updated = new_updated
|
1272
|
+
end
|
1273
|
+
|
1274
|
+
# Returns the feed item issued time
|
1275
|
+
def issued
|
1276
|
+
if @issued.nil?
|
1277
|
+
unless root_node.nil?
|
1278
|
+
issued_string = XPath.first(root_node, "issued/text()").to_s
|
1279
|
+
if issued_string == ""
|
1280
|
+
issued_string = XPath.first(root_node, "published/text()").to_s
|
1281
|
+
end
|
1282
|
+
if issued_string == ""
|
1283
|
+
issued_string = XPath.first(root_node, "pubDate/text()").to_s
|
1284
|
+
end
|
1285
|
+
if issued_string == ""
|
1286
|
+
issued_string = XPath.first(root_node, "dc:date/text()").to_s
|
1287
|
+
end
|
1288
|
+
end
|
1289
|
+
if issued_string != nil && issued_string != ""
|
1290
|
+
@issued = Time.parse(issued_string) rescue nil
|
1291
|
+
else
|
1292
|
+
@issued = nil
|
1293
|
+
end
|
1294
|
+
end
|
1295
|
+
return @issued
|
1296
|
+
end
|
1297
|
+
|
1298
|
+
# Sets the feed item issued time
|
1299
|
+
def issued=(new_issued)
|
1300
|
+
@issued = new_issued
|
1301
|
+
end
|
1302
|
+
|
1303
|
+
# Returns the url for posting comments
|
1304
|
+
def comments
|
1305
|
+
if @comments.nil?
|
1306
|
+
@comments = FeedTools.normalize_url(
|
1307
|
+
XPath.first(root_node, "comments/text()").to_s)
|
1308
|
+
@comments = nil if @comments == ""
|
1309
|
+
end
|
1310
|
+
return @comments
|
1311
|
+
end
|
1312
|
+
|
1313
|
+
# Sets the url for posting comments
|
1314
|
+
def comments=(new_comments)
|
1315
|
+
@comments = new_comments
|
1316
|
+
end
|
1317
|
+
|
1318
|
+
# The source that this post was based on
|
1319
|
+
def source
|
1320
|
+
if @source.nil?
|
1321
|
+
@source = FeedTools::Feed::Link.new
|
1322
|
+
@source.url = XPath.first(root_node, "source/@url").to_s
|
1323
|
+
@source.url = nil if @source.url == ""
|
1324
|
+
@source.value = XPath.first(root_node, "source/text()").to_s
|
1325
|
+
@source.value = nil if @source.value == ""
|
1326
|
+
end
|
1327
|
+
return @source
|
1328
|
+
end
|
1329
|
+
|
1330
|
+
# Returns the feed item tags
|
1331
|
+
def tags
|
1332
|
+
# TODO: support the rel="tag" microformat
|
1333
|
+
# =======================================
|
1334
|
+
if @tags.nil?
|
1335
|
+
@tags = []
|
1336
|
+
if @tags.nil? || @tags.size == 0
|
1337
|
+
@tags = []
|
1338
|
+
tag_list = XPath.match(root_node, "dc:subject/rdf:Bag/rdf:li/text()")
|
1339
|
+
if tag_list.size > 1
|
1340
|
+
for tag in tag_list
|
1341
|
+
@tags << tag.to_s.downcase.strip
|
1342
|
+
end
|
1343
|
+
end
|
1344
|
+
end
|
1345
|
+
if @tags.nil? || @tags.size == 0
|
1346
|
+
# messy effort to find ourselves some tags, mainly for del.icio.us
|
1347
|
+
@tags = []
|
1348
|
+
rdf_bag = XPath.match(root_node, "taxo:topics/rdf:Bag/rdf:li")
|
1349
|
+
if rdf_bag != nil && rdf_bag.size > 0
|
1350
|
+
for tag_node in rdf_bag
|
1351
|
+
begin
|
1352
|
+
tag_url = XPath.first(root_node, "@resource").to_s
|
1353
|
+
tag_match = tag_url.scan(/\/(tag|tags)\/(\w+)/)
|
1354
|
+
if tag_match.size > 0
|
1355
|
+
@tags << tag_match.first.last.downcase.strip
|
1356
|
+
end
|
1357
|
+
rescue
|
1358
|
+
end
|
1359
|
+
end
|
1360
|
+
end
|
1361
|
+
end
|
1362
|
+
if @tags.nil? || @tags.size == 0
|
1363
|
+
@tags = []
|
1364
|
+
tag_list = XPath.match(root_node, "category/text()")
|
1365
|
+
for tag in tag_list
|
1366
|
+
@tags << tag.to_s.downcase.strip
|
1367
|
+
end
|
1368
|
+
end
|
1369
|
+
if @tags.nil? || @tags.size == 0
|
1370
|
+
@tags = []
|
1371
|
+
tag_list = XPath.match(root_node, "dc:subject/text()")
|
1372
|
+
for tag in tag_list
|
1373
|
+
@tags << tag.to_s.downcase.strip
|
1374
|
+
end
|
1375
|
+
end
|
1376
|
+
if @tags.nil? || @tags.size == 0
|
1377
|
+
begin
|
1378
|
+
@tags = XPath.first(root_node, "itunes:keywords/text()").to_s.downcase.split(" ")
|
1379
|
+
rescue
|
1380
|
+
@tags = []
|
1381
|
+
end
|
1382
|
+
end
|
1383
|
+
if @tags.nil?
|
1384
|
+
@tags = []
|
1385
|
+
end
|
1386
|
+
@tags.uniq!
|
1387
|
+
end
|
1388
|
+
return @tags
|
1389
|
+
end
|
1390
|
+
|
1391
|
+
# Sets the feed item tags
|
1392
|
+
def tags=(new_tags)
|
1393
|
+
@tags = new_tags
|
1394
|
+
end
|
1395
|
+
|
1396
|
+
# Returns true if this feed item contains explicit material. If the whole
|
1397
|
+
# feed has been marked as explicit, this will return true even if the item
|
1398
|
+
# isn't explicitly marked as explicit.
|
1399
|
+
def explicit?
|
1400
|
+
if @explicit.nil?
|
1401
|
+
if XPath.first(root_node,
|
1402
|
+
"media:adult/text()").to_s.downcase == "true" ||
|
1403
|
+
XPath.first(root_node,
|
1404
|
+
"itunes:explicit/text()").to_s.downcase == "yes" ||
|
1405
|
+
XPath.first(root_node,
|
1406
|
+
"itunes:explicit/text()").to_s.downcase == "true" ||
|
1407
|
+
feed.explicit?
|
1408
|
+
@explicit = true
|
1409
|
+
else
|
1410
|
+
@explicit = false
|
1411
|
+
end
|
1412
|
+
end
|
1413
|
+
return @explicit
|
1414
|
+
end
|
1415
|
+
|
1416
|
+
# Sets whether or not the feed contains explicit material
|
1417
|
+
def explicit=(new_explicit)
|
1418
|
+
@explicit = (new_explicit ? true : false)
|
1419
|
+
end
|
1420
|
+
|
1421
|
+
# A hook method that is called during the feed generation process. Overriding this method
|
1422
|
+
# will enable additional content to be inserted into the feed.
|
1423
|
+
def build_xml_hook(feed_type, version, xml_builder)
|
1424
|
+
return nil
|
1425
|
+
end
|
1426
|
+
|
1427
|
+
# Generates xml based on the content of the feed item
|
1428
|
+
def build_xml(feed_type=(self.feed.feed_type or "rss"), version=nil,
|
1429
|
+
xml_builder=Builder::XmlMarkup.new(:indent => 2))
|
1430
|
+
if feed_type == "rss" && (version == nil || version == 0.0)
|
1431
|
+
version = 1.0
|
1432
|
+
elsif feed_type == "atom" && (version == nil || version == 0.0)
|
1433
|
+
version = 0.3
|
1434
|
+
end
|
1435
|
+
if feed_type == "rss" && (version == 0.9 || version == 1.0 || version == 1.1)
|
1436
|
+
# RDF-based rss format
|
1437
|
+
if link.nil?
|
1438
|
+
raise "Cannot generate an rdf-based feed item with a nil link field."
|
1439
|
+
end
|
1440
|
+
return xml_builder.item("rdf:about" => CGI.escapeHTML(link)) do
|
1441
|
+
unless title.nil? || title == ""
|
1442
|
+
xml_builder.title(title)
|
1443
|
+
else
|
1444
|
+
xml_builder.title
|
1445
|
+
end
|
1446
|
+
unless link.nil? || link == ""
|
1447
|
+
xml_builder.link(link)
|
1448
|
+
else
|
1449
|
+
xml_builder.link
|
1450
|
+
end
|
1451
|
+
unless description.nil? || description == ""
|
1452
|
+
xml_builder.description(description)
|
1453
|
+
else
|
1454
|
+
xml_builder.description
|
1455
|
+
end
|
1456
|
+
unless time.nil?
|
1457
|
+
xml_builder.tag!("dc:date", time.iso8601)
|
1458
|
+
end
|
1459
|
+
unless tags.nil? || tags.size == 0
|
1460
|
+
xml_builder.tag!("taxo:topics") do
|
1461
|
+
xml_builder.tag!("rdf:Bag") do
|
1462
|
+
for tag in tags
|
1463
|
+
xml_builder.tag!("rdf:li", tag)
|
1464
|
+
end
|
1465
|
+
end
|
1466
|
+
end
|
1467
|
+
xml_builder.tag!("itunes:keywords", tags.join(" "))
|
1468
|
+
end
|
1469
|
+
build_xml_hook(feed_type, version, xml_builder)
|
1470
|
+
end
|
1471
|
+
elsif feed_type == "rss"
|
1472
|
+
# normal rss format
|
1473
|
+
return xml_builder.item do
|
1474
|
+
unless title.nil? || title == ""
|
1475
|
+
xml_builder.title(title)
|
1476
|
+
end
|
1477
|
+
unless link.nil? || link == ""
|
1478
|
+
xml_builder.link(link)
|
1479
|
+
end
|
1480
|
+
unless description.nil? || description == ""
|
1481
|
+
xml_builder.description(description)
|
1482
|
+
end
|
1483
|
+
unless time.nil?
|
1484
|
+
xml_builder.pubDate(time.rfc822)
|
1485
|
+
end
|
1486
|
+
unless tags.nil? || tags.size == 0
|
1487
|
+
xml_builder.tag!("taxo:topics") do
|
1488
|
+
xml_builder.tag!("rdf:Bag") do
|
1489
|
+
for tag in tags
|
1490
|
+
xml_builder.tag!("rdf:li", tag)
|
1491
|
+
end
|
1492
|
+
end
|
1493
|
+
end
|
1494
|
+
xml_builder.tag!("itunes:keywords", tags.join(" "))
|
1495
|
+
end
|
1496
|
+
build_xml_hook(feed_type, version, xml_builder)
|
1497
|
+
end
|
1498
|
+
elsif feed_type == "atom" && version == 0.3
|
1499
|
+
# normal atom format
|
1500
|
+
return xml_builder.entry("xmlns" => "http://purl.org/atom/ns#") do
|
1501
|
+
unless title.nil? || title == ""
|
1502
|
+
xml_builder.title(title,
|
1503
|
+
"mode" => "escaped",
|
1504
|
+
"type" => "text/html")
|
1505
|
+
end
|
1506
|
+
xml_builder.author do
|
1507
|
+
unless self.author.nil? || self.author.name.nil?
|
1508
|
+
xml_builder.name(self.author.name)
|
1509
|
+
else
|
1510
|
+
xml_builder.name("n/a")
|
1511
|
+
end
|
1512
|
+
unless self.author.nil? || self.author.email.nil?
|
1513
|
+
xml_builder.email(self.author.email)
|
1514
|
+
end
|
1515
|
+
unless self.author.nil? || self.author.url.nil?
|
1516
|
+
xml_builder.url(self.author.url)
|
1517
|
+
end
|
1518
|
+
end
|
1519
|
+
unless link.nil? || link == ""
|
1520
|
+
xml_builder.link("href" => link,
|
1521
|
+
"rel" => "alternate",
|
1522
|
+
"type" => "text/html",
|
1523
|
+
"title" => title)
|
1524
|
+
end
|
1525
|
+
unless description.nil? || description == ""
|
1526
|
+
xml_builder.content(description,
|
1527
|
+
"mode" => "escaped",
|
1528
|
+
"type" => "text/html")
|
1529
|
+
end
|
1530
|
+
unless time.nil?
|
1531
|
+
xml_builder.issued(time.iso8601)
|
1532
|
+
end
|
1533
|
+
unless tags.nil? || tags.size == 0
|
1534
|
+
for tag in tags
|
1535
|
+
xml_builder.category(tag)
|
1536
|
+
end
|
1537
|
+
end
|
1538
|
+
build_xml_hook(feed_type, version, xml_builder)
|
1539
|
+
end
|
1540
|
+
elsif feed_type == "atom" && version == 1.0
|
1541
|
+
# normal atom format
|
1542
|
+
return xml_builder.entry("xmlns" => "http://www.w3.org/2005/Atom") do
|
1543
|
+
unless title.nil? || title == ""
|
1544
|
+
xml_builder.title(title,
|
1545
|
+
"type" => "html")
|
1546
|
+
end
|
1547
|
+
xml_builder.author do
|
1548
|
+
unless self.author.nil? || self.author.name.nil?
|
1549
|
+
xml_builder.name(self.author.name)
|
1550
|
+
else
|
1551
|
+
xml_builder.name("n/a")
|
1552
|
+
end
|
1553
|
+
unless self.author.nil? || self.author.email.nil?
|
1554
|
+
xml_builder.email(self.author.email)
|
1555
|
+
end
|
1556
|
+
unless self.author.nil? || self.author.url.nil?
|
1557
|
+
xml_builder.url(self.author.url)
|
1558
|
+
end
|
1559
|
+
end
|
1560
|
+
unless link.nil? || link == ""
|
1561
|
+
xml_builder.link("href" => link,
|
1562
|
+
"rel" => "alternate",
|
1563
|
+
"type" => "text/html",
|
1564
|
+
"title" => title)
|
1565
|
+
end
|
1566
|
+
unless description.nil? || description == ""
|
1567
|
+
xml_builder.content(description,
|
1568
|
+
"type" => "html")
|
1569
|
+
else
|
1570
|
+
xml_builder.content(FeedTools.no_content_string,
|
1571
|
+
"type" => "html")
|
1572
|
+
end
|
1573
|
+
if self.updated != nil
|
1574
|
+
xml_builder.updated(self.updated.iso8601)
|
1575
|
+
elsif self.time != nil
|
1576
|
+
# Not technically correct, but a heck of a lot better
|
1577
|
+
# than the Time.now fall-back.
|
1578
|
+
xml_builder.updated(self.time.iso8601)
|
1579
|
+
else
|
1580
|
+
xml_builder.updated(Time.now.iso8601)
|
1581
|
+
end
|
1582
|
+
unless self.published.nil?
|
1583
|
+
xml_builder.published(self.published.iso8601)
|
1584
|
+
end
|
1585
|
+
if self.id != nil
|
1586
|
+
unless FeedTools.is_uri? self.id
|
1587
|
+
if self.time != nil && self.link != nil
|
1588
|
+
xml_builder.id(FeedTools.build_tag_uri(self.link, self.time))
|
1589
|
+
elsif self.link != nil
|
1590
|
+
xml_builder.id(FeedTools.build_urn_uuid_uri(self.link))
|
1591
|
+
else
|
1592
|
+
raise "The unique id must be a URI. " +
|
1593
|
+
"(Attempted to generate id, but failed.)"
|
1594
|
+
end
|
1595
|
+
else
|
1596
|
+
xml_builder.id(self.id)
|
1597
|
+
end
|
1598
|
+
elsif self.time != nil && self.link != nil
|
1599
|
+
xml_builder.id(FeedTools.build_tag_uri(self.link, self.time))
|
1600
|
+
else
|
1601
|
+
raise "Cannot build feed, missing feed unique id."
|
1602
|
+
end
|
1603
|
+
unless self.tags.nil? || self.tags.size == 0
|
1604
|
+
for tag in self.tags
|
1605
|
+
xml_builder.category("term" => tag)
|
1606
|
+
end
|
1607
|
+
end
|
1608
|
+
build_xml_hook(feed_type, version, xml_builder)
|
1609
|
+
end
|
1610
|
+
end
|
1611
|
+
end
|
1612
|
+
|
1613
|
+
alias_method :tagline, :description
|
1614
|
+
alias_method :tagline=, :description=
|
1615
|
+
alias_method :subtitle, :description
|
1616
|
+
alias_method :subtitle=, :description=
|
1617
|
+
alias_method :summary, :description
|
1618
|
+
alias_method :summary=, :description=
|
1619
|
+
alias_method :abstract, :description
|
1620
|
+
alias_method :abstract=, :description=
|
1621
|
+
alias_method :content, :description
|
1622
|
+
alias_method :content=, :description=
|
1623
|
+
alias_method :guid, :id
|
1624
|
+
alias_method :guid=, :id=
|
1625
|
+
alias_method :published, :issued
|
1626
|
+
alias_method :published=, :issued=
|
1627
|
+
|
1628
|
+
# Returns a simple representation of the feed item object's state.
|
1629
|
+
def inspect
|
1630
|
+
return "#<FeedTools::FeedItem:0x#{self.object_id.to_s(16)} " +
|
1631
|
+
"LINK:#{self.link}>"
|
1632
|
+
end
|
1633
|
+
end
|
1634
|
+
end
|