#--
# Copyright (c) 2005 Robert Aman
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
module FeedTools
# The FeedTools::FeedItem class represents the structure of
# a single item within a web feed.
class FeedItem
# :stopdoc:
include REXML
include GenericHelper
private :validate_options
# :startdoc:
# This class stores information about a feed item's file enclosures.
class Enclosure
# The url for the enclosure
attr_accessor :url
# The MIME type of the file referenced by the enclosure
attr_accessor :type
# The size of the file referenced by the enclosure
attr_accessor :file_size
# The total play time of the file referenced by the enclosure
attr_accessor :duration
# The height in pixels of the enclosed media
attr_accessor :height
# The width in pixels of the enclosed media
attr_accessor :width
# The bitrate of the enclosed media
attr_accessor :bitrate
# The framerate of the enclosed media
attr_accessor :framerate
# The thumbnail for this enclosure
attr_accessor :thumbnail
# The categories for this enclosure
attr_accessor :categories
# A hash of the enclosed file
attr_accessor :hash
# A website containing some kind of media player instead of a direct
# link to the media file.
attr_accessor :player
# A list of credits for the enclosed media
attr_accessor :credits
# A text rendition of the enclosed media
attr_accessor :text
# A list of alternate version of the enclosed media file
attr_accessor :versions
# The default version of the enclosed media file
attr_accessor :default_version
# Returns true if this is the default enclosure
def is_default?
return @is_default
end
# Sets whether this is the default enclosure for the media group
def is_default=(new_is_default)
@is_default = new_is_default
end
# Returns true if the enclosure contains explicit material
def explicit?
return @explicit
end
# Sets the explicit attribute on the enclosure
def explicit=(new_explicit)
@explicit = new_explicit
end
# Determines if the object is a sample, or the full version of the
# object, or if it is a stream.
# Possible values are 'sample', 'full', 'nonstop'.
def expression
return @expression
end
# Sets the expression attribute on the enclosure.
# Allowed values are 'sample', 'full', 'nonstop'.
def expression=(new_expression)
unless ['sample', 'full', 'nonstop'].include? new_expression.downcase
raise ArgumentError,
"Permitted values are 'sample', 'full', 'nonstop'."
end
@expression = new_expression.downcase
end
# Returns true if this enclosure contains audio content
def audio?
unless self.type.nil?
return true if (self.type =~ /^audio/) != nil
end
# TODO: create a more complete list
# =================================
audio_extensions = ['mp3', 'm4a', 'm4p', 'wav', 'ogg', 'wma']
audio_extensions.each do |extension|
if (url =~ /#{extension}$/) != nil
return true
end
end
return false
end
# Returns true if this enclosure contains video content
def video?
unless self.type.nil?
return true if (self.type =~ /^video/) != nil
return true if self.type == "image/mov"
end
# TODO: create a more complete list
# =================================
video_extensions = ['mov', 'mp4', 'avi', 'wmv', 'asf']
video_extensions.each do |extension|
if (url =~ /#{extension}$/) != nil
return true
end
end
return false
end
alias_method :link, :url
alias_method :link=, :url=
end
# TODO: Make these actual classes instead of structs
# ==================================================
EnclosureHash = Struct.new( "EnclosureHash", :hash, :type )
EnclosurePlayer = Struct.new( "EnclosurePlayer", :url, :height, :width )
EnclosureCredit = Struct.new( "EnclosureCredit", :name, :role )
EnclosureThumbnail = Struct.new( "EnclosureThumbnail", :url, :height,
:width )
# Initialize the feed object
def initialize
super
@feed_data = nil
@feed_data_type = :xml
@xml_doc = nil
@root_node = nil
@title = nil
@id = nil
@time = Time.now.gmtime
end
# Returns the parent feed of this feed item
# Warning, this method may be slow if you have a
# large number of FeedTools::Feed objects. Can't
# use a direct reference to the parent because it plays
# havoc with the garbage collector. Could've used
# a WeakRef object, but really, if there are multiple
# parent feeds, something is going to go wrong, and the
# programmer needs to be notified. A WeakRef
# implementation can't detect this condition.
def feed
parent_feed = nil
ObjectSpace.each_object(FeedTools::Feed) do |feed|
if feed.instance_variable_get("@entries").nil?
feed.items
end
unsorted_items = feed.instance_variable_get("@entries")
for item in unsorted_items
if item.object_id == self.object_id
if parent_feed.nil?
parent_feed = feed
break
else
raise "Multiple parent feeds found."
end
end
end
end
return parent_feed
end
# Returns the feed item's raw data.
def feed_data
return @feed_data
end
# Sets the feed item's data.
def feed_data=(new_feed_data)
@time = nil
@feed_data = new_feed_data
end
# Returns the feed item's data type.
def feed_data_type
return @feed_data_type
end
# Sets the feed item's data type.
def feed_data_type=(new_feed_data_type)
@feed_data_type = new_feed_data_type
end
# Returns a REXML Document of the feed_data
def xml
if self.feed_data_type != :xml
@xml_doc = nil
else
if @xml_doc.nil?
# TODO: :ignore_whitespace_nodes => :all
# Add that?
# ======================================
@xml_doc = Document.new(self.feed_data)
end
end
return @xml_doc
end
# Returns the first node within the root_node that matches the xpath query.
def find_node(xpath, select_result_value=false)
if feed.feed_data_type != :xml
raise "The feed data type is not xml."
end
return try_xpaths(self.root_node, [xpath],
:select_result_value => select_result_value)
end
# Returns all nodes within the root_node that match the xpath query.
def find_all_nodes(xpath, select_result_value=false)
if feed.feed_data_type != :xml
raise "The feed data type is not xml."
end
return try_xpaths_all(self.root_node, [xpath],
:select_result_value => select_result_value)
end
# Returns the root node of the feed item.
def root_node
if @root_node.nil?
if xml.nil?
return nil
end
@root_node = xml.root
end
return @root_node
end
# Returns the feed items's unique id
def id
if @id.nil?
@id = try_xpaths(self.root_node, [
"atom10:id/text()",
"atom03:id/text()",
"atom:id/text()",
"id/text()",
"guid/text()"
], :select_result_value => true)
end
return @id
end
# Sets the feed item's unique id
def id=(new_id)
@id = new_id
end
# Returns the feed item title
def title
if @title.nil?
repair_entities = false
title_node = try_xpaths(self.root_node, [
"atom10:title",
"atom03:title",
"atom:title",
"title",
"dc:title"
])
if title_node.nil?
return nil
end
title_type = try_xpaths(title_node, "@type",
:select_result_value => true)
title_mode = try_xpaths(title_node, "@mode",
:select_result_value => true)
title_encoding = try_xpaths(title_node, "@encoding",
:select_result_value => true)
# Note that we're checking for misuse of type, mode and encoding here
if title_type == "base64" || title_mode == "base64" ||
title_encoding == "base64"
@title = Base64.decode64(title_node.inner_xml.strip)
elsif title_type == "xhtml" || title_mode == "xhtml" ||
title_type == "xml" || title_mode == "xml" ||
title_type == "application/xhtml+xml"
@title = title_node.inner_xml
elsif title_type == "escaped" || title_mode == "escaped"
@title = FeedTools.unescape_entities(
title_node.inner_xml)
else
@title = title_node.inner_xml
repair_entities = true
end
unless @title.nil?
@title = FeedTools.sanitize_html(@title, :strip)
@title = FeedTools.unescape_entities(@title) if repair_entities
@title = FeedTools.tidy_html(@title) unless repair_entities
end
if !@title.blank? && FeedTools.configurations[:strip_comment_count]
# Some blogging tools include the number of comments in a post
# in the title... this is supremely ugly, and breaks any
# applications which expect the title to be static, so we're
# gonna strip them out.
#
# If for some incredibly wierd reason you need the actual
# unstripped title, just use find_node("title/text()").to_s
@title = @title.strip.gsub(/\[\d*\]$/, "").strip
end
@title.gsub!(/>\n, "><")
@title.gsub!(/\n/, " ")
@title.strip!
@title = nil if @title.blank?
end
return @title
end
# Sets the feed item title
def title=(new_title)
@title = new_title
end
# Returns the feed item content
def content
if @content.nil?
repair_entities = false
content_node = try_xpaths(self.root_node, [
"content:encoded",
"content",
"fullitem",
"xhtml:body",
"body",
"encoded",
"description",
"tagline",
"subtitle",
"summary",
"abstract",
"blurb",
"info"
])
if content_node.nil?
return nil
end
content_type = try_xpaths(content_node, "@type",
:select_result_value => true)
content_mode = try_xpaths(content_node, "@mode",
:select_result_value => true)
content_encoding = try_xpaths(content_node, "@encoding",
:select_result_value => true)
# Note that we're checking for misuse of type, mode and encoding here
if !content_encoding.blank?
@content =
"[Embedded data objects are not currently supported.]"
elsif content_node.cdatas.size > 0
@content = content_node.cdatas.first.value
elsif content_type == "base64" || content_mode == "base64" ||
content_encoding == "base64"
@content = Base64.decode64(content_node.inner_xml.strip)
elsif content_type == "xhtml" || content_mode == "xhtml" ||
content_type == "xml" || content_mode == "xml" ||
content_type == "application/xhtml+xml"
@content = content_node.inner_xml
elsif content_type == "escaped" || content_mode == "escaped"
@content = FeedTools.unescape_entities(
content_node.inner_xml)
else
@content = content_node.inner_xml
repair_entities = true
end
if @content.blank?
@content = self.itunes_summary
end
if @content.blank?
@content = self.itunes_subtitle
end
unless @content.blank?
@content = FeedTools.sanitize_html(@content, :strip)
@content = FeedTools.unescape_entities(@content) if repair_entities
@content = FeedTools.tidy_html(@content)
end
@content = @content.strip unless @content.nil?
@content = nil if @content.blank?
end
return @content
end
# Sets the feed item content
def content=(new_content)
@content = new_content
end
# Returns the contents of the itunes:summary element
def itunes_summary
if @itunes_summary.nil?
@itunes_summary = try_xpaths(self.root_node, [
"itunes:summary/text()"
])
unless @itunes_summary.blank?
@itunes_summary = FeedTools.unescape_entities(@itunes_summary)
@itunes_summary = FeedTools.sanitize_html(@itunes_summary)
else
@itunes_summary = nil
end
end
return @itunes_summary
end
# Sets the contents of the itunes:summary element
def itunes_summary=(new_itunes_summary)
@itunes_summary = new_itunes_summary
end
# Returns the contents of the itunes:subtitle element
def itunes_subtitle
if @itunes_subtitle.nil?
@itunes_subtitle = try_xpaths(self.root_node, [
"itunes:subtitle/text()"
])
unless @itunes_subtitle.blank?
@itunes_subtitle = FeedTools.unescape_entities(@itunes_subtitle)
@itunes_subtitle = FeedTools.sanitize_html(@itunes_subtitle)
else
@itunes_subtitle = nil
end
end
return @itunes_subtitle
end
# Sets the contents of the itunes:subtitle element
def itunes_subtitle=(new_itunes_subtitle)
@itunes_subtitle = new_itunes_subtitle
end
# Returns the contents of the media:text element
def media_text
if @media_text.nil?
@media_text = FeedTools.unescape_entities(XPath.first(root_node,
"itunes:subtitle/text()").to_s)
if @media_text == ""
@media_text = nil
end
unless @media_text.nil?
@media_text = FeedTools.sanitize_html(@media_text)
end
end
return @media_text
end
# Sets the contents of the media:text element
def media_text=(new_media_text)
@media_text = new_media_text
end
# Returns the feed item link
def link
if @link.nil?
@link = try_xpaths(self.root_node, [
"atom10:link[@type='application/xhtml+xml']/@href",
"atom10:link[@type='text/html']/@href",
"atom10:link[@rel='alternate']/@href",
"atom03:link[@type='application/xhtml+xml']/@href",
"atom03:link[@type='text/html']/@href",
"atom03:link[@rel='alternate']/@href",
"atom:link[@type='application/xhtml+xml']/@href",
"atom:link[@type='text/html']/@href",
"atom:link[@rel='alternate']/@href",
"link[@type='application/xhtml+xml']/@href",
"link[@type='text/html']/@href",
"link[@rel='alternate']/@href",
"link/text()",
"@rdf:about",
"guid[@isPermaLink='true']/text()",
"@href",
"a/@href"
], :select_result_value => true)
if @link.blank?
if FeedTools.is_uri? self.guid
@link = self.guid
end
end
if !@link.blank?
@link = FeedTools.unescape_entities(@link)
end
# TODO: Actually implement proper relative url resolving instead of this crap
# ===========================================================================
#
# if @link != "" && (@link =~ /http:\/\//) != 0 && (@link =~ /https:\/\//) != 0
# if (feed.base[-1..-1] == "/" && @link[0..0] == "/")
# @link = @link[1..-1]
# end
# # prepend the base to the link since they seem to have used a relative path
# @link = feed.base + @link
# end
if @link.blank?
link_node = try_xpaths(self.root_node, [
"atom10:link",
"atom03:link",
"atom:link",
"link"
])
if link_node != nil
if link_node.attributes['type'].to_s =~ /^image/ ||
link_node.attributes['type'].to_s =~ /^application/ ||
link_node.attributes['type'].to_s =~ /xml/ ||
link_node.attributes['rel'].to_s =~ /self/
for child in self.root_node
if child.class == REXML::Element
if child.name.downcase == "link"
if child.attributes['type'].to_s =~ /^image/ ||
child.attributes['type'].to_s =~ /^application/ ||
child.attributes['type'].to_s =~ /xml/ ||
child.attributes['rel'].to_s =~ /self/
@link = nil
next
else
@link = child.attributes['href'].to_s
if @link.blank?
@link = child.inner_xml
end
if @link.blank?
next
end
break
end
end
end
end
else
@link = link_node.attributes['href'].to_s
end
end
end
@link = self.comments if @link.blank?
@link = nil if @link.blank?
if FeedTools.configurations[:url_normalization_enabled]
@link = FeedTools.normalize_url(@link)
end
end
return @link
end
# Sets the feed item link
def link=(new_link)
@link = new_link
end
# Returns a list of the feed item's categories
def categories
if @categories.nil?
@categories = []
category_nodes = try_xpaths_all(self.root_node, [
"category",
"dc:subject"
])
for category_node in category_nodes
category = FeedTools::Feed::Category.new
category.term = try_xpaths(category_node, ["@term", "text()"],
:select_result_value => true)
category.term.strip! unless category.term.nil?
category.label = try_xpaths(category_node, ["@label"],
:select_result_value => true)
category.label.strip! unless category.label.nil?
category.scheme = try_xpaths(category_node, [
"@scheme",
"@domain"
], :select_result_value => true)
category.scheme.strip! unless category.scheme.nil?
@categories << category
end
end
return @categories
end
# Returns a list of the feed items's images
def images
if @images.nil?
@images = []
image_nodes = try_xpaths_all(self.root_node, [
"image",
"logo",
"atom10:link",
"atom03:link",
"atom:link",
"link"
])
unless image_nodes.blank?
for image_node in image_nodes
image = FeedTools::Feed::Image.new
image.url = try_xpaths(image_node, [
"url/text()",
"@rdf:resource"
], :select_result_value => true)
if image.url.blank? && (image_node.name == "logo" ||
(image_node.attributes['type'].to_s =~ /^image/) == 0)
image.url = try_xpaths(image_node, [
"@atom10:href",
"@atom03:href",
"@atom:href",
"@href"
], :select_result_value => true)
if image.url == self.link && image.url != nil
image.url = nil
end
end
if image.url.blank? && image_node.name == "LOGO"
image.url = try_xpaths(image_node, [
"@href"
], :select_result_value => true)
end
image.url.strip! unless image.url.nil?
image.title = try_xpaths(image_node,
["title/text()"], :select_result_value => true)
image.title.strip! unless image.title.nil?
image.description = try_xpaths(image_node,
["description/text()"], :select_result_value => true)
image.description.strip! unless image.description.nil?
image.link = try_xpaths(image_node,
["link/text()"], :select_result_value => true)
image.link.strip! unless image.link.nil?
image.height = try_xpaths(image_node,
["height/text()"], :select_result_value => true).to_i
image.height = nil if image.height <= 0
image.width = try_xpaths(image_node,
["width/text()"], :select_result_value => true).to_i
image.width = nil if image.width <= 0
image.style = try_xpaths(image_node, [
"style/text()",
"@style"
], :select_result_value => true)
image.style.strip! unless image.style.nil?
image.style.downcase! unless image.style.nil?
@images << image unless image.url.nil?
end
end
end
return @images
end
# Returns the feed item itunes image link
def itunes_image_link
if @itunes_image_link.nil?
@itunes_image_link = try_xpaths(self.root_node, [
"itunes:image/@href",
"itunes:link[@rel='image']/@href"
], :select_result_value => true)
if FeedTools.configurations[:url_normalization_enabled]
@itunes_image_link = FeedTools.normalize_url(@itunes_image_link)
end
end
return @itunes_image_link
end
# Sets the feed item itunes image link
def itunes_image_link=(new_itunes_image_link)
@itunes_image_link = new_itunes_image_link
end
# Returns the feed item media thumbnail link
def media_thumbnail_link
if @media_thumbnail_link.nil?
@media_thumbnail_link = try_xpaths(self.root_node, [
"media:thumbnail/@url"
], :select_result_value => true)
if FeedTools.configurations[:url_normalization_enabled]
@media_thumbnail_link = FeedTools.normalize_url(@media_thumbnail_link)
end
end
return @media_thumbnail_link
end
# Sets the feed item media thumbnail url
def media_thumbnail_link=(new_media_thumbnail_link)
@media_thumbnail_link = new_media_thumbnail_link
end
# Returns the feed item's copyright information
def copyright
if @copyright.nil?
repair_entities = false
copyright_node = try_xpaths(self.root_node, [
"atom10:copyright",
"atom03:copyright",
"atom:copyright",
"copyright",
"copyrights",
"dc:rights",
"rights"
])
if copyright_node.nil?
return nil
end
copyright_type = try_xpaths(copyright_node, "@type",
:select_result_value => true)
copyright_mode = try_xpaths(copyright_node, "@mode",
:select_result_value => true)
copyright_encoding = try_xpaths(copyright_node, "@encoding",
:select_result_value => true)
# Note that we're checking for misuse of type, mode and encoding here
if !copyright_encoding.blank?
@copyright =
"[Embedded data objects are not currently supported.]"
elsif copyright_node.cdatas.size > 0
@copyright = copyright_node.cdatas.first.value
elsif copyright_type == "base64" || copyright_mode == "base64" ||
copyright_encoding == "base64"
@copyright = Base64.decode64(copyright_node.inner_xml.strip)
elsif copyright_type == "xhtml" || copyright_mode == "xhtml" ||
copyright_type == "xml" || copyright_mode == "xml" ||
copyright_type == "application/xhtml+xml"
@copyright = copyright_node.inner_xml
elsif copyright_type == "escaped" || copyright_mode == "escaped"
@copyright = FeedTools.unescape_entities(
copyright_node.inner_xml)
else
@copyright = copyright_node.inner_xml
repair_entities = true
end
unless @copyright.nil?
@copyright = FeedTools.sanitize_html(@copyright, :strip)
@copyright = FeedTools.unescape_entities(@copyright) if repair_entities
@copyright = FeedTools.tidy_html(@copyright)
end
@copyright = @copyright.strip unless @copyright.nil?
@copyright = nil if @copyright.blank?
end
return @copyright
end
# Sets the feed item's copyright information
def copyright=(new_copyright)
@copyright = new_copyright
end
# Returns all feed item enclosures
def enclosures
if @enclosures.nil?
@enclosures = []
# First, load up all the different possible sources of enclosures
rss_enclosures =
try_xpaths_all(self.root_node, ["enclosure"])
atom_enclosures =
try_xpaths_all(self.root_node, [
"atom10:link[@rel='enclosure']",
"atom03:link[@rel='enclosure']",
"atom:link[@rel='enclosure']",
"link[@rel='enclosure']"
])
media_content_enclosures =
try_xpaths_all(self.root_node, ["media:content"])
media_group_enclosures =
try_xpaths_all(self.root_node, ["media:group"])
# Parse RSS-type enclosures. Thanks to a few buggy enclosures
# implementations, sometimes these also manage to show up in atom
# files.
for enclosure_node in rss_enclosures
enclosure = Enclosure.new
enclosure.url = FeedTools.unescape_entities(
enclosure_node.attributes["url"].to_s)
enclosure.type = enclosure_node.attributes["type"].to_s
enclosure.file_size = enclosure_node.attributes["length"].to_i
enclosure.credits = []
enclosure.explicit = false
@enclosures << enclosure
end
# Parse atom-type enclosures. If there are repeats of the same
# enclosure object, we merge the two together.
for enclosure_node in atom_enclosures
enclosure_url = FeedTools.unescape_entities(
enclosure_node.attributes["href"].to_s)
enclosure = nil
new_enclosure = false
for existing_enclosure in @enclosures
if existing_enclosure.url == enclosure_url
enclosure = existing_enclosure
break
end
end
if enclosure.nil?
new_enclosure = true
enclosure = Enclosure.new
end
enclosure.url = enclosure_url
enclosure.type = enclosure_node.attributes["type"].to_s
enclosure.file_size = enclosure_node.attributes["length"].to_i
enclosure.credits = []
enclosure.explicit = false
if new_enclosure
@enclosures << enclosure
end
end
# Creates an anonymous method to parse content objects from the media
# module. We do this to avoid excessive duplication of code since we
# have to do identical processing for content objects within group
# objects.
parse_media_content = lambda do |media_content_nodes|
affected_enclosures = []
for enclosure_node in media_content_nodes
enclosure_url = FeedTools.unescape_entities(
enclosure_node.attributes["url"].to_s)
enclosure = nil
new_enclosure = false
for existing_enclosure in @enclosures
if existing_enclosure.url == enclosure_url
enclosure = existing_enclosure
break
end
end
if enclosure.nil?
new_enclosure = true
enclosure = Enclosure.new
end
enclosure.url = enclosure_url
enclosure.type = enclosure_node.attributes["type"].to_s
enclosure.file_size = enclosure_node.attributes["fileSize"].to_i
enclosure.duration = enclosure_node.attributes["duration"].to_s
enclosure.height = enclosure_node.attributes["height"].to_i
enclosure.width = enclosure_node.attributes["width"].to_i
enclosure.bitrate = enclosure_node.attributes["bitrate"].to_i
enclosure.framerate = enclosure_node.attributes["framerate"].to_i
enclosure.expression =
enclosure_node.attributes["expression"].to_s
enclosure.is_default =
(enclosure_node.attributes["isDefault"].to_s.downcase == "true")
enclosure_thumbnail_url = try_xpaths(enclosure_node,
["media:thumbnail/@url"], :select_result_value => true)
if !enclosure_thumbnail_url.blank?
enclosure.thumbnail = EnclosureThumbnail.new(
FeedTools.unescape_entities(enclosure_thumbnail_url),
FeedTools.unescape_entities(
try_xpaths(enclosure_node, ["media:thumbnail/@height"],
:select_result_value => true)),
FeedTools.unescape_entities(
try_xpaths(enclosure_node, ["media:thumbnail/@width"],
:select_result_value => true))
)
end
enclosure.categories = []
for category in try_xpaths_all(enclosure_node, ["media:category"])
enclosure.categories << FeedTools::Feed::Category.new
enclosure.categories.last.term =
FeedTools.unescape_entities(category.inner_xml)
enclosure.categories.last.scheme =
FeedTools.unescape_entities(
category.attributes["scheme"].to_s)
enclosure.categories.last.label =
FeedTools.unescape_entities(
category.attributes["label"].to_s)
if enclosure.categories.last.scheme.blank?
enclosure.categories.last.scheme = nil
end
if enclosure.categories.last.label.blank?
enclosure.categories.last.label = nil
end
end
enclosure_media_hash = try_xpaths(enclosure_node,
["media:hash/text()"], :select_result_value => true)
if !enclosure_media_hash.nil?
enclosure.hash = EnclosureHash.new(
FeedTools.sanitize_html(FeedTools.unescape_entities(
enclosure_media_hash), :strip),
"md5"
)
end
enclosure_media_player_url = try_xpaths(enclosure_node,
["media:player/@url"], :select_result_value => true)
if !enclosure_media_player_url.blank?
enclosure.player = EnclosurePlayer.new(
FeedTools.unescape_entities(enclosure_media_player_url),
FeedTools.unescape_entities(
try_xpaths(enclosure_node,
["media:player/@height"], :select_result_value => true)),
FeedTools.unescape_entities(
try_xpaths(enclosure_node,
["media:player/@width"], :select_result_value => true))
)
end
enclosure.credits = []
for credit in try_xpaths_all(enclosure_node, ["media:credit"])
enclosure.credits << EnclosureCredit.new(
FeedTools.unescape_entities(credit.inner_xml.to_s.strip),
FeedTools.unescape_entities(
credit.attributes["role"].to_s.downcase)
)
if enclosure.credits.last.name.blank?
enclosure.credits.last.name = nil
end
if enclosure.credits.last.role.blank?
enclosure.credits.last.role = nil
end
end
enclosure.explicit = (try_xpaths(enclosure_node,
["media:adult/text()"]).to_s.downcase == "true")
enclosure_media_text =
try_xpaths(enclosure_node, ["media:text/text()"])
if !enclosure_media_text.blank?
enclosure.text = FeedTools.unescape_entities(
enclosure_media_text)
end
affected_enclosures << enclosure
if new_enclosure
@enclosures << enclosure
end
end
affected_enclosures
end
# Parse the independant content objects.
parse_media_content.call(media_content_enclosures)
media_groups = []
# Parse the group objects.
for media_group in media_group_enclosures
group_media_content_enclosures =
try_xpaths_all(media_group, ["media:content"])
# Parse the content objects within the group objects.
affected_enclosures =
parse_media_content.call(group_media_content_enclosures)
# Now make sure that content objects inherit certain properties from
# the group objects.
for enclosure in affected_enclosures
media_group_thumbnail = try_xpaths(media_group,
["media:thumbnail/@url"], :select_result_value => true)
if enclosure.thumbnail.nil? && !media_group_thumbnail.blank?
enclosure.thumbnail = EnclosureThumbnail.new(
FeedTools.unescape_entities(
media_group_thumbnail),
FeedTools.unescape_entities(
try_xpaths(media_group, ["media:thumbnail/@height"],
:select_result_value => true)),
FeedTools.unescape_entities(
try_xpaths(media_group, ["media:thumbnail/@width"],
:select_result_value => true))
)
end
if (enclosure.categories.blank?)
enclosure.categories = []
for category in try_xpaths_all(media_group, ["media:category"])
enclosure.categories << FeedTools::Feed::Category.new
enclosure.categories.last.term =
FeedTools.unescape_entities(category.inner_xml)
enclosure.categories.last.scheme =
FeedTools.unescape_entities(
category.attributes["scheme"].to_s)
enclosure.categories.last.label =
FeedTools.unescape_entities(
category.attributes["label"].to_s)
if enclosure.categories.last.scheme.blank?
enclosure.categories.last.scheme = nil
end
if enclosure.categories.last.label.blank?
enclosure.categories.last.label = nil
end
end
end
enclosure_media_group_hash = try_xpaths(enclosure_node,
["media:hash/text()"], :select_result_value => true)
if enclosure.hash.nil? && !enclosure_media_group_hash.blank?
enclosure.hash = EnclosureHash.new(
FeedTools.sanitize_html(FeedTools.unescape_entities(
enclosure_media_group_hash), :strip),
"md5"
)
end
enclosure_media_group_url = try_xpaths(media_group,
"media:player/@url", :select_result_value => true)
if enclosure.player.nil? && !enclosure_media_group_url.blank?
enclosure.player = EnclosurePlayer.new(
FeedTools.unescape_entities(enclosure_media_group_url),
FeedTools.unescape_entities(
try_xpaths(media_group, ["media:player/@height"],
:select_result_value => true)),
FeedTools.unescape_entities(
try_xpaths(media_group, ["media:player/@width"],
:select_result_value => true))
)
end
if enclosure.credits.nil? || enclosure.credits.size == 0
enclosure.credits = []
for credit in try_xpaths_all(media_group, ["media:credit"])
enclosure.credits << EnclosureCredit.new(
FeedTools.unescape_entities(credit.inner_xml),
FeedTools.unescape_entities(
credit.attributes["role"].to_s.downcase)
)
if enclosure.credits.last.role.blank?
enclosure.credits.last.role = nil
end
end
end
if enclosure.explicit?.nil?
enclosure.explicit = ((try_xpaths(media_group, [
"media:adult/text()"
], :select_result_value => true).downcase == "true") ?
true : false)
end
enclosure_media_group_text = try_xpaths(media_group,
["media:text/text()"], :select_result_value => true)
if enclosure.text.nil? && !enclosure_media_group_text.blank?
enclosure.text = FeedTools.sanitize_html(
FeedTools.unescape_entities(
enclosure_media_group_text), :strip)
end
end
# Keep track of the media groups
media_groups << affected_enclosures
end
# Now we need to inherit any relevant item level information.
if self.explicit?
for enclosure in @enclosures
enclosure.explicit = true
end
end
# Add all the itunes categories
itunes_categories =
try_xpaths_all(self.root_node, ["itunes:category"])
for itunes_category in itunes_categories
genre = "Podcasts"
category = itunes_category.attributes["text"].to_s
subcategory =
try_xpaths(itunes_category, ["itunes:category/@text"],
:select_result_value => true)
category_path = genre
if category != ""
category_path << "/" + category
end
if subcategory != ""
category_path << "/" + subcategory
end
for enclosure in @enclosures
if enclosure.categories.nil?
enclosure.categories = []
end
enclosure.categories << FeedTools::Feed::Category.new
enclosure.categories.last.term =
FeedTools.unescape_entities(category_path)
enclosure.categories.last.scheme =
"http://www.apple.com/itunes/store/"
enclosure.categories.last.label =
"iTunes Music Store Categories"
end
end
for enclosure in @enclosures
# Clean up any of those attributes that incorrectly have ""
# or 0 as their values
if enclosure.type.blank?
enclosure.type = nil
end
if enclosure.file_size == 0
enclosure.file_size = nil
end
if enclosure.duration == 0
enclosure.duration = nil
end
if enclosure.height == 0
enclosure.height = nil
end
if enclosure.width == 0
enclosure.width = nil
end
if enclosure.bitrate == 0
enclosure.bitrate = nil
end
if enclosure.framerate == 0
enclosure.framerate = nil
end
if enclosure.expression.blank?
enclosure.expression = "full"
end
# If an enclosure is missing the text field, fall back on the
# itunes:summary field
if enclosure.text.blank?
enclosure.text = self.itunes_summary
end
# Make sure we don't have duplicate categories
unless enclosure.categories.nil?
enclosure.categories.uniq!
end
end
# And finally, now things get complicated. This is where we make
# sure that the enclosures method only returns either default
# enclosures or enclosures with only one version. Any enclosures
# that are wrapped in a media:group will be placed in the appropriate
# versions field.
affected_enclosure_urls = []
for media_group in media_groups
affected_enclosure_urls =
affected_enclosure_urls | (media_group.map do |enclosure|
enclosure.url
end)
end
@enclosures.delete_if do |enclosure|
(affected_enclosure_urls.include? enclosure.url)
end
for media_group in media_groups
default_enclosure = nil
for enclosure in media_group
if enclosure.is_default?
default_enclosure = enclosure
end
end
for enclosure in media_group
enclosure.default_version = default_enclosure
enclosure.versions = media_group.clone
enclosure.versions.delete(enclosure)
end
@enclosures << default_enclosure
end
end
# If we have a single enclosure, it's safe to inherit the
# itunes:duration field if it's missing.
if @enclosures.size == 1
if @enclosures.first.duration.nil? || @enclosures.first.duration == 0
@enclosures.first.duration = self.itunes_duration
end
end
return @enclosures
end
def enclosures=(new_enclosures)
@enclosures = new_enclosures
end
# Returns the feed item author
def author
if @author.nil?
@author = FeedTools::Feed::Author.new
author_node = try_xpaths(self.root_node, [
"atom10:author",
"atom03:author",
"atom:author",
"author",
"managingEditor",
"dc:author",
"dc:creator",
"creator"
])
unless author_node.nil?
@author.raw = FeedTools.unescape_entities(
XPath.first(author_node, "text()").to_s).strip
@author.raw = nil if @author.raw.blank?
unless @author.raw.nil?
raw_scan = @author.raw.scan(
/(.*)\((\b[A-Z0-9._%-\+]+@[A-Z0-9._%-]+\.[A-Z]{2,4}\b)\)/i)
if raw_scan.nil? || raw_scan.size == 0
raw_scan = @author.raw.scan(
/(\b[A-Z0-9._%-\+]+@[A-Z0-9._%-]+\.[A-Z]{2,4}\b)\s*\((.*)\)/i)
unless raw_scan.size == 0
author_raw_pair = raw_scan.first.reverse
end
else
author_raw_pair = raw_scan.first
end
if raw_scan.nil? || raw_scan.size == 0
email_scan = @author.raw.scan(
/\b[A-Z0-9._%-\+]+@[A-Z0-9._%-]+\.[A-Z]{2,4}\b/i)
if email_scan != nil && email_scan.size > 0
@author.email = email_scan.first.strip
end
end
unless author_raw_pair.nil? || author_raw_pair.size == 0
@author.name = author_raw_pair.first.strip
@author.email = author_raw_pair.last.strip
else
unless @author.raw.include?("@")
# We can be reasonably sure we are looking at something
# that the creator didn't intend to contain an email address
# if it got through the preceeding regexes and it doesn't
# contain the tell-tale '@' symbol.
@author.name = @author.raw
end
end
end
if @author.name.blank?
@author.name = FeedTools.unescape_entities(
try_xpaths(author_node, [
"name/text()",
"@name"
], :select_result_value => true)
)
end
if @author.email.blank?
@author.email = FeedTools.unescape_entities(
try_xpaths(author_node, [
"email/text()",
"@email"
], :select_result_value => true)
)
end
if @author.url.blank?
@author.url = FeedTools.unescape_entities(
try_xpaths(author_node, [
"url/text()",
"uri/text()",
"@url",
"@uri",
"@href"
], :select_result_value => true)
)
end
@author.name = nil if @author.name.blank?
@author.raw = nil if @author.raw.blank?
@author.email = nil if @author.email.blank?
@author.url = nil if @author.url.blank?
end
# Fallback on the itunes module if we didn't find an author name
begin
@author.name = self.itunes_author if @author.name.nil?
rescue
@author.name = nil
end
end
return @author
end
# Sets the feed item author
def author=(new_author)
if new_author.respond_to?(:name) &&
new_author.respond_to?(:email) &&
new_author.respond_to?(:url)
# It's a complete author object, just set it.
@author = new_author
else
# We're not looking at an author object, this is probably a string,
# default to setting the author's name.
if @author.nil?
@author = FeedTools::Feed::Author.new
end
@author.name = new_author
end
end
# Returns the feed publisher
def publisher
if @publisher.nil?
@publisher = FeedTools::Feed::Author.new
# Set the author name
@publisher.raw = FeedTools.unescape_entities(
try_xpaths(self.root_node, [
"dc:publisher/text()",
"webMaster/text()"
], :select_result_value => true))
unless @publisher.raw.blank?
raw_scan = @publisher.raw.scan(
/(.*)\((\b[A-Z0-9._%-\+]+@[A-Z0-9._%-]+\.[A-Z]{2,4}\b)\)/i)
if raw_scan.nil? || raw_scan.size == 0
raw_scan = @publisher.raw.scan(
/(\b[A-Z0-9._%-\+]+@[A-Z0-9._%-]+\.[A-Z]{2,4}\b)\s*\((.*)\)/i)
unless raw_scan.size == 0
publisher_raw_pair = raw_scan.first.reverse
end
else
publisher_raw_pair = raw_scan.first
end
if raw_scan.nil? || raw_scan.size == 0
email_scan = @publisher.raw.scan(
/\b[A-Z0-9._%-\+]+@[A-Z0-9._%-]+\.[A-Z]{2,4}\b/i)
if email_scan != nil && email_scan.size > 0
@publisher.email = email_scan.first.strip
end
end
unless publisher_raw_pair.nil? || publisher_raw_pair.size == 0
@publisher.name = publisher_raw_pair.first.strip
@publisher.email = publisher_raw_pair.last.strip
else
unless @publisher.raw.include?("@")
# We can be reasonably sure we are looking at something
# that the creator didn't intend to contain an email address if
# it got through the preceeding regexes and it doesn't
# contain the tell-tale '@' symbol.
@publisher.name = @publisher.raw
end
end
end
@publisher.name = nil if @publisher.name.blank?
@publisher.raw = nil if @publisher.raw.blank?
@publisher.email = nil if @publisher.email.blank?
@publisher.url = nil if @publisher.url.blank?
end
return @publisher
end
# Sets the feed publisher
def publisher=(new_publisher)
if new_publisher.respond_to?(:name) &&
new_publisher.respond_to?(:email) &&
new_publisher.respond_to?(:url)
# It's a complete Author object, just set it.
@publisher = new_publisher
else
# We're not looking at an Author object, this is probably a string,
# default to setting the publisher's name.
if @publisher.nil?
@publisher = FeedTools::Feed::Author.new
end
@publisher.name = new_publisher
end
end
# Returns the contents of the itunes:author element
#
# This inherits from any incorrectly placed channel-level itunes:author
# elements. They're actually amazingly common. People don't read specs.
def itunes_author
if @itunes_author.nil?
@itunes_author = FeedTools.unescape_entities(
try_xpaths(self.root_node,
["itunes:author/text()"], :select_result_value => true))
@itunes_author = feed.itunes_author if @itunes_author.blank?
end
return @itunes_author
end
# Sets the contents of the itunes:author element
def itunes_author=(new_itunes_author)
@itunes_author = new_itunes_author
end
# Returns the number of seconds that the associated media runs for
def itunes_duration
if @itunes_duration.nil?
raw_duration = FeedTools.unescape_entities(
try_xpaths(self.root_node,
["itunes:duration/text()"], :select_result_value => true))
if !raw_duration.blank?
hms = raw_duration.split(":").map { |x| x.to_i }
if hms.size == 3
@itunes_duration = hms[0].hours + hms[1].minutes + hms[2]
elsif hms.size == 2
@itunes_duration = hms[0].minutes + hms[1]
elsif hms.size == 1
@itunes_duration = hms[0]
end
end
end
return @itunes_duration
end
# Sets the number of seconds that the associate media runs for
def itunes_duration=(new_itunes_duration)
@itunes_duration = new_itunes_duration
end
# Returns the feed item time
def time(options = {})
validate_options([ :estimate_timestamp ],
options.keys)
options = { :estimate_timestamp => true }.merge(options)
if @time.nil?
time_string = try_xpaths(self.root_node, [
"atom10:updated/text()",
"atom03:updated/text()",
"atom:updated/text()",
"updated/text()",
"atom10:modified/text()",
"atom03:modified/text()",
"atom:modified/text()",
"modified/text()",
"time/text()",
"atom10:issued/text()",
"atom03:issued/text()",
"atom:issued/text()",
"issued/text()",
"atom10:published/text()",
"atom03:published/text()",
"atom:published/text()",
"published/text()",
"pubDate/text()",
"dc:date/text()",
"date/text()"
], :select_result_value => true)
begin
if !time_string.blank?
@time = Time.parse(time_string).gmtime
elsif FeedTools.configurations[:timestamp_estimation_enabled] &&
!self.title.nil? &&
(Time.parse(self.title) - Time.now).abs > 100
@time = Time.parse(self.title).gmtime
end
rescue
end
if FeedTools.configurations[:timestamp_estimation_enabled]
if options[:estimate_timestamp]
if @time.nil?
begin
@time = succ_time
if @time.nil?
@time = prev_time
end
rescue
end
if @time.nil?
@time = Time.now.gmtime
end
end
end
end
end
return @time
end
# Sets the feed item time
def time=(new_time)
@time = new_time
end
# Returns 1 second after the previous item's time.
def succ_time #:nodoc:
begin
parent_feed = self.feed
if parent_feed.nil?
return nil
end
if parent_feed.instance_variable_get("@entries").nil?
parent_feed.items
end
unsorted_items = parent_feed.instance_variable_get("@entries")
item_index = unsorted_items.index(self)
if item_index.nil?
return nil
end
if item_index <= 0
return nil
end
previous_item = unsorted_items[item_index - 1]
return (previous_item.time(:estimate_timestamp => false) + 1)
rescue
return nil
end
end
private :succ_time
# Returns 1 second before the succeeding item's time.
def prev_time #:nodoc:
begin
parent_feed = self.feed
if parent_feed.nil?
return nil
end
if parent_feed.instance_variable_get("@entries").nil?
parent_feed.items
end
unsorted_items = parent_feed.instance_variable_get("@entries")
item_index = unsorted_items.index(self)
if item_index.nil?
return nil
end
if item_index >= (unsorted_items.size - 1)
return nil
end
succeeding_item = unsorted_items[item_index + 1]
return (succeeding_item.time(:estimate_timestamp => false) - 1)
rescue
return nil
end
end
private :prev_time
# Returns the feed item updated time
def updated
if @updated.nil?
updated_string = try_xpaths(self.root_node, [
"atom10:updated/text()",
"atom03:updated/text()",
"atom:updated/text()",
"updated/text()",
"atom10:modified/text()",
"atom03:modified/text()",
"atom:modified/text()",
"modified/text()"
], :select_result_value => true)
if !updated_string.blank?
@updated = Time.parse(updated_string).gmtime rescue nil
else
@updated = nil
end
end
return @updated
end
# Sets the feed item updated time
def updated=(new_updated)
@updated = new_updated
end
# Returns the feed item published time
def published
if @published.nil?
published_string = try_xpaths(self.root_node, [
"atom10:issued/text()",
"atom03:issued/text()",
"atom:issued/text()",
"issued/text()",
"atom10:published/text()",
"atom03:published/text()",
"atom:published/text()",
"published/text()",
"pubDate/text()",
"dc:date/text()",
"date/text()"
], :select_result_value => true)
if !published_string.blank?
@issued = Time.parse(published_string).gmtime rescue nil
else
@issued = nil
end
end
return @issued
end
# Sets the feed item published time
def published=(new_published)
@published = new_published
end
# Returns the url for posting comments
def comments
if @comments.nil?
@comments = try_xpaths(self.root_node, ["comments/text()"],
:select_result_value => true)
if FeedTools.configurations[:url_normalization_enabled]
@comments = FeedTools.normalize_url(@comments)
end
end
return @comments
end
# Sets the url for posting comments
def comments=(new_comments)
@comments = new_comments
end
# The source that this post was based on
def source
if @source.nil?
@source = FeedTools::Feed::Link.new
@source.url = try_xpaths(self.root_node, ["source/@url"],
:select_result_value => true)
@source.value = try_xpaths(self.root_node, ["source/text()"],
:select_result_value => true)
end
return @source
end
# Returns the feed item tags
def tags
# TODO: support the rel="tag" microformat
# =======================================
if @tags.nil?
@tags = []
if root_node.nil?
return @tags
end
if @tags.nil? || @tags.size == 0
@tags = []
tag_list = try_xpaths_all(self.root_node,
["dc:subject/rdf:Bag/rdf:li/text()"],
:select_result_value => true)
if tag_list != nil && tag_list.size > 0
for tag in tag_list
@tags << tag.downcase.strip
end
end
end
if @tags.nil? || @tags.size == 0
# messy effort to find ourselves some tags, mainly for del.icio.us
@tags = []
rdf_bag = try_xpaths_all(self.root_node,
["taxo:topics/rdf:Bag/rdf:li"])
if rdf_bag != nil && rdf_bag.size > 0
for tag_node in rdf_bag
begin
tag_url = try_xpaths(tag_node, ["@resource"],
:select_result_value => true)
tag_match = tag_url.scan(/\/(tag|tags)\/(\w+)$/)
if tag_match.size > 0
@tags << tag_match.first.last.downcase.strip
end
rescue
end
end
end
end
if @tags.nil? || @tags.size == 0
@tags = []
tag_list = try_xpaths_all(self.root_node, ["category/text()"],
:select_result_value => true)
for tag in tag_list
@tags << tag.to_s.downcase.strip
end
end
if @tags.nil? || @tags.size == 0
@tags = []
tag_list = try_xpaths_all(self.root_node, ["dc:subject/text()"],
:select_result_value => true)
for tag in tag_list
@tags << tag.to_s.downcase.strip
end
end
if @tags.blank?
begin
itunes_keywords_string = try_xpaths(self.root_node, [
"itunes:keywords/text()"
], :select_result_value => true)
unless itunes_keywords_string.blank?
@tags = itunes_keywords_string.downcase.split(",")
if @tags.size == 1
@tags = itunes_keywords_string.downcase.split(" ")
@tags = @tags.map { |tag| tag.chomp(",") }
end
if @tags.size == 1
@tags = itunes_keywords_string.downcase.split(",")
end
@tags = @tags.map { |tag| tag.strip }
end
rescue
@tags = []
end
end
if @tags.nil?
@tags = []
end
@tags.uniq!
end
return @tags
end
# Sets the feed item tags
def tags=(new_tags)
@tags = new_tags
end
# Returns true if this feed item contains explicit material. If the whole
# feed has been marked as explicit, this will return true even if the item
# isn't explicitly marked as explicit.
def explicit?
if @explicit.nil?
explicit_string = try_xpaths(self.root_node, [
"media:adult/text()",
"itunes:explicit/text()"
], :select_result_value => true)
if explicit_string == "true" || explicit_string == "yes" ||
feed.explicit?
@explicit = true
else
@explicit = false
end
end
return @explicit
end
# Sets whether or not the feed contains explicit material
def explicit=(new_explicit)
@explicit = (new_explicit ? true : false)
end
# A hook method that is called during the feed generation process. Overriding this method
# will enable additional content to be inserted into the feed.
def build_xml_hook(feed_type, version, xml_builder)
return nil
end
# Generates xml based on the content of the feed item
def build_xml(feed_type=(self.feed.feed_type or "atom"), version=nil,
xml_builder=Builder::XmlMarkup.new(
:indent => 2, :escape_attrs => false))
if feed_type == "rss" && (version == nil || version == 0.0)
version = 1.0
elsif feed_type == "atom" && (version == nil || version == 0.0)
version = 1.0
end
if feed_type == "rss" && (version == 0.9 || version == 1.0 || version == 1.1)
# RDF-based rss format
if link.nil?
raise "Cannot generate an rdf-based feed item with a nil link field."
end
return xml_builder.item("rdf:about" =>
FeedTools.escape_entities(link)) do
unless title.nil? || title == ""
xml_builder.title(title)
else
xml_builder.title
end
unless link.nil? || link == ""
xml_builder.link(link)
else
xml_builder.link
end
unless description.nil? || description == ""
xml_builder.description(description)
else
xml_builder.description
end
unless time.nil?
xml_builder.tag!("dc:date", time.iso8601)
end
unless tags.nil? || tags.size == 0
xml_builder.tag!("taxo:topics") do
xml_builder.tag!("rdf:Bag") do
for tag in tags
xml_builder.tag!("rdf:li", tag)
end
end
end
if self.feed.podcast?
xml_builder.tag!("itunes:keywords", tags.join(", "))
end
end
build_xml_hook(feed_type, version, xml_builder)
end
elsif feed_type == "rss"
# normal rss format
return xml_builder.item do
unless self.title.blank?
xml_builder.title(self.title)
end
unless self.link.blank?
xml_builder.link(link)
end
unless self.description.blank?
xml_builder.description(self.description)
end
unless self.time.nil?
xml_builder.pubDate(self.time.rfc822)
end
unless self.guid.blank?
if FeedTools.is_uri?(self.guid)
xml_builder.guid(self.guid, "isPermaLink" => "true")
else
xml_builder.guid(self.guid, "isPermaLink" => "false")
end
else
unless self.link.blank?
xml_builder.guid(self.link, "isPermaLink" => "true")
end
end
unless tags.nil? || tags.size == 0
xml_builder.tag!("taxo:topics") do
xml_builder.tag!("rdf:Bag") do
for tag in tags
xml_builder.tag!("rdf:li", tag)
end
end
end
if self.feed.podcast?
xml_builder.tag!("itunes:keywords", tags.join(", "))
end
end
unless self.enclosures.blank? || self.enclosures.size == 0
for enclosure in self.enclosures
attribute_hash = {}
next if enclosure.url.blank?
begin
if enclosure.file_size.blank? || enclosure.file_size.to_i == 0
# We can't use this enclosure because it's missing the
# required file size. Check alternate versions for
# file_size.
if !enclosure.versions.blank? && enclosure.versions.size > 0
for alternate in enclosure.versions
if alternate.file_size != nil &&
alternate.file_size.to_i > 0
enclosure = alternate
break
end
end
end
end
rescue
end
attribute_hash["url"] = FeedTools.normalize_url(enclosure.url)
if enclosure.type != nil
attribute_hash["type"] = enclosure.type
end
if enclosure.file_size != nil && enclosure.file_size.to_i > 0
attribute_hash["length"] = enclosure.file_size.to_s
else
# We couldn't find an alternate and the problem is still
# there. Give up and go on.
xml_builder.comment!(
"*** Enclosure failed to include file size. Ignoring. ***")
next
end
xml_builder.enclosure(attribute_hash)
end
end
build_xml_hook(feed_type, version, xml_builder)
end
elsif feed_type == "atom" && version == 0.3
raise "Atom 0.3 is obsolete."
elsif feed_type == "atom" && version == 1.0
# normal atom format
return xml_builder.entry("xmlns" =>
FEED_TOOLS_NAMESPACES['atom10']) do
unless title.nil? || title == ""
xml_builder.title(title,
"type" => "html")
end
xml_builder.author do
unless self.author.nil? || self.author.name.nil?
xml_builder.name(self.author.name)
else
xml_builder.name("n/a")
end
unless self.author.nil? || self.author.email.nil?
xml_builder.email(self.author.email)
end
unless self.author.nil? || self.author.url.nil?
xml_builder.uri(self.author.url)
end
end
unless link.nil? || link == ""
xml_builder.link("href" => FeedTools.escape_entities(self.link),
"rel" => "alternate",
"type" => "text/html",
"title" => FeedTools.escape_entities(title))
end
if !description.blank?
xml_builder.content(description,
"type" => "html")
elsif !FeedTools.configurations[:no_content_string].blank?
xml_builder.content(FeedTools.configurations[:no_content_string],
"type" => "html")
end
if self.updated != nil
xml_builder.updated(self.updated.iso8601)
elsif self.time != nil
# Not technically correct, but a heck of a lot better
# than the Time.now fall-back.
xml_builder.updated(self.time.iso8601)
else
xml_builder.updated(Time.now.gmtime.iso8601)
end
unless self.published.nil?
xml_builder.published(self.published.iso8601)
end
if self.id != nil
unless FeedTools.is_uri? self.id
if self.time != nil && self.link != nil
xml_builder.id(FeedTools.build_tag_uri(self.link, self.time))
elsif self.link != nil
xml_builder.id(FeedTools.build_urn_uuid_uri(self.link))
else
raise "The unique id must be a URI. " +
"(Attempted to generate id, but failed.)"
end
else
xml_builder.id(self.id)
end
elsif self.time != nil && self.link != nil
xml_builder.id(FeedTools.build_tag_uri(self.link, self.time))
else
raise "Cannot build feed, missing feed unique id."
end
unless self.tags.nil? || self.tags.size == 0
for tag in self.tags
xml_builder.category("term" => tag)
end
end
unless self.enclosures.blank? || self.enclosures.size == 0
for enclosure in self.enclosures
attribute_hash = {}
next if enclosure.url.blank?
attribute_hash["rel"] = "enclosure"
attribute_hash["href"] = FeedTools.normalize_url(enclosure.url)
if enclosure.type != nil
attribute_hash["type"] = enclosure.type
end
if enclosure.file_size != nil && enclosure.file_size.to_i > 0
attribute_hash["length"] = enclosure.file_size.to_s
end
xml_builder.link(attribute_hash)
end
end
build_xml_hook(feed_type, version, xml_builder)
end
else
raise "Unsupported feed format/version."
end
end
alias_method :summary, :content
alias_method :summary=, :content=
alias_method :abstract, :content
alias_method :abstract=, :content=
alias_method :description, :content
alias_method :description=, :content=
alias_method :guid, :id
alias_method :guid=, :id=
# Returns a simple representation of the feed item object's state.
def inspect
return "#"
end
end
end