#--
# 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.
def feed
parent_feed = nil
ObjectSpace.each_object(FeedTools::Feed) do |feed|
if feed.instance_variable_get("@items").nil?
feed.items
end
unsorted_items = feed.instance_variable_get("@items")
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)
return XPath.first(root_node, xpath)
end
# Returns all nodes within the root_node that match the xpath query.
def find_all_nodes(xpath)
return XPath.match(root_node, xpath)
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?
unless root_node.nil?
@id = XPath.first(root_node, "id/text()").to_s
if @id == ""
@id = XPath.first(root_node, "guid/text()").to_s
end
end
@id = nil if @id == ""
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?
unless root_node.nil?
repair_entities = false
title_node = XPath.first(root_node, "atom10:title",
FEED_TOOLS_NAMESPACES)
if title_node.nil?
title_node = XPath.first(root_node, "title")
end
if title_node.nil?
title_node = XPath.first(root_node, "atom03:title",
FEED_TOOLS_NAMESPACES)
end
if title_node.nil?
title_node = XPath.first(root_node, "atom:title")
end
if title_node.nil?
title_node = XPath.first(root_node, "dc:title",
FEED_TOOLS_NAMESPACES)
end
if title_node.nil?
title_node = XPath.first(root_node, "dc:title")
end
if title_node.nil?
title_node = XPath.first(root_node, "TITLE")
end
end
if title_node.nil?
return nil
end
title_type = XPath.first(title_node, "@type").to_s
title_mode = XPath.first(title_node, "@mode").to_s
title_encoding = XPath.first(title_node, "@encoding").to_s
# 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 != ""
# 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 == ""
end
return @title
end
# Sets the feed item title
def title=(new_title)
@title = new_title
end
# Returns the feed item description
def description
if @description.nil?
unless root_node.nil?
repair_entities = false
description_node = XPath.first(root_node, "content:encoded")
if description_node.nil?
description_node = XPath.first(root_node, "content:encoded",
FEED_TOOLS_NAMESPACES)
end
if description_node.nil?
description_node = XPath.first(root_node, "encoded")
end
if description_node.nil?
description_node = XPath.first(root_node, "content")
end
if description_node.nil?
description_node = XPath.first(root_node, "fullitem")
end
if description_node.nil?
description_node = XPath.first(root_node, "xhtml:body")
end
if description_node.nil?
description_node = XPath.first(root_node, "xhtml:body",
FEED_TOOLS_NAMESPACES)
end
if description_node.nil?
description_node = XPath.first(root_node, "body")
end
if description_node.nil?
description_node = XPath.first(root_node, "description")
end
if description_node.nil?
description_node = XPath.first(root_node, "tagline")
end
if description_node.nil?
description_node = XPath.first(root_node, "subtitle")
end
if description_node.nil?
description_node = XPath.first(root_node, "summary")
end
if description_node.nil?
description_node = XPath.first(root_node, "abstract")
end
if description_node.nil?
description_node = XPath.first(root_node, "ABSTRACT")
end
if description_node.nil?
description_node = XPath.first(root_node, "blurb")
end
if description_node.nil?
description_node = XPath.first(root_node, "info")
end
end
if description_node.nil?
return nil
end
description_type = XPath.first(description_node, "@type").to_s
description_mode = XPath.first(description_node, "@mode").to_s
description_encoding = XPath.first(description_node, "@encoding").to_s
# Note that we're checking for misuse of type, mode and encoding here
if description_encoding != ""
@description =
"[Embedded data objects are not currently supported.]"
elsif description_node.cdatas.size > 0
@description = description_node.cdatas.first.value
elsif description_type == "base64" || description_mode == "base64" ||
description_encoding == "base64"
@description = Base64.decode64(description_node.inner_xml.strip)
elsif description_type == "xhtml" || description_mode == "xhtml" ||
description_type == "xml" || description_mode == "xml" ||
description_type == "application/xhtml+xml"
@description = description_node.inner_xml
elsif description_type == "escaped" || description_mode == "escaped"
@description = FeedTools.unescape_entities(
description_node.inner_xml)
else
@description = description_node.inner_xml
repair_entities = true
end
if @description == ""
@description = self.itunes_summary
@description = "" if @description.nil?
end
if @description == ""
@description = self.itunes_subtitle
@description = "" if @description.nil?
end
unless @description.nil?
@description = FeedTools.sanitize_html(@description, :strip)
@description = FeedTools.unescape_entities(@description) if repair_entities
@description = FeedTools.tidy_html(@description)
end
@description = @description.strip unless @description.nil?
@description = nil if @description == ""
end
return @description
end
# Sets the feed item description
def description=(new_description)
@description = new_description
end
# Returns the contents of the itunes:summary element
def itunes_summary
if @itunes_summary.nil?
@itunes_summary = FeedTools.unescape_entities(XPath.first(root_node,
"itunes:summary/text()").to_s)
if @itunes_summary == ""
@itunes_summary = nil
end
unless @itunes_summary.nil?
@itunes_summary = FeedTools.sanitize_html(@itunes_summary)
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 = FeedTools.unescape_entities(XPath.first(root_node,
"itunes:subtitle/text()").to_s)
if @itunes_subtitle == ""
@itunes_subtitle = nil
end
unless @itunes_subtitle.nil?
@itunes_subtitle = FeedTools.sanitize_html(@itunes_subtitle)
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?
unless root_node.nil?
@link = XPath.first(root_node, "link[@rel='alternate']/@href").to_s
if @link == ""
@link = XPath.first(root_node, "link/@href").to_s
end
if @link == ""
@link = XPath.first(root_node, "link/text()").to_s
end
if @link == ""
@link = XPath.first(root_node, "@rdf:about").to_s
end
if @link == ""
@link = XPath.first(root_node, "guid[@isPermaLink='true']/text()").to_s
end
if @link == ""
@link = XPath.first(root_node, "@href").to_s
end
if @link == ""
@link = XPath.first(root_node, "a/@href").to_s
end
if @link == ""
@link = XPath.first(root_node, "@HREF").to_s
end
if @link == ""
@link = XPath.first(root_node, "A/@HREF").to_s
end
end
if @link == "" || @link.nil?
if FeedTools.is_uri? self.guid
@link = self.guid
end
end
if @link != ""
@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
@link = FeedTools.normalize_url(@link)
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 = XPath.match(root_node, "category")
if category_nodes.nil? || category_nodes.empty?
category_nodes = XPath.match(root_node, "dc:subject")
end
unless category_nodes.nil?
for category_node in category_nodes
category = FeedTools::Feed::Category.new
category.term = XPath.first(category_node, "@term").to_s
if category.term == ""
category.term = XPath.first(category_node, "text()").to_s
end
category.term.strip! unless category.term.nil?
category.term = nil if category.term == ""
category.label = XPath.first(category_node, "@label").to_s
category.label.strip! unless category.label.nil?
category.label = nil if category.label == ""
category.scheme = XPath.first(category_node, "@scheme").to_s
if category.scheme == ""
category.scheme = XPath.first(category_node, "@domain").to_s
end
category.scheme.strip! unless category.scheme.nil?
category.scheme = nil if category.scheme == ""
@categories << category
end
end
end
return @categories
end
# Returns a list of the feed items's images
def images
if @images.nil?
@images = []
image_nodes = XPath.match(root_node, "link")
if image_nodes.nil? || image_nodes.empty?
image_nodes = XPath.match(root_node, "logo")
end
if image_nodes.nil? || image_nodes.empty?
image_nodes = XPath.match(root_node, "LOGO")
end
if image_nodes.nil? || image_nodes.empty?
image_nodes = XPath.match(root_node, "image")
end
unless image_nodes.nil?
for image_node in image_nodes
image = FeedTools::Feed::Image.new
image.url = XPath.first(image_node, "url/text()").to_s
if image.url == ""
image.url = XPath.first(image_node, "@rdf:resource").to_s
end
if image.url == "" && (image_node.name == "logo" ||
(image_node.attributes['type'] =~ /^image/) == 0)
image.url = XPath.first(image_node, "@href").to_s
end
if image.url == "" && image_node.name == "LOGO"
image.url = XPath.first(image_node, "@HREF").to_s
end
image.url.strip! unless image.url.nil?
image.url = nil if image.url == ""
image.title = XPath.first(image_node, "title/text()").to_s
image.title.strip! unless image.title.nil?
image.title = nil if image.title == ""
image.description =
XPath.first(image_node, "description/text()").to_s
image.description.strip! unless image.description.nil?
image.description = nil if image.description == ""
image.link = XPath.first(image_node, "link/text()").to_s
image.link.strip! unless image.link.nil?
image.link = nil if image.link == ""
image.height = XPath.first(image_node, "height/text()").to_s.to_i
image.height = nil if image.height <= 0
image.width = XPath.first(image_node, "width/text()").to_s.to_i
image.width = nil if image.width <= 0
image.style = XPath.first(image_node, "@style").to_s.downcase
if image.style == ""
image.style = XPath.first(image_node, "@STYLE").to_s.downcase
end
image.style.strip! unless image.style.nil?
image.style = nil if image.style == ""
@images << image
end
end
end
return @images
end
# Returns the feed item itunes image link
#
# If it's not present, falls back to the normal image link.
# Technically, the itunes spec says that the image needs to be
# square and larger than 300x300, but hey, if there's an image
# to be had, it's better than none at all.
def itunes_image_link
if @itunes_image_link.nil?
# get the feed item itunes image link from the xml document
@itunes_image_link = XPath.first(root_node, "itunes:image/@href").to_s
if @itunes_image_link == ""
@itunes_image_link = XPath.first(root_node, "itunes:link[@rel='image']/@href").to_s
end
@itunes_image_link = FeedTools.normalize_url(@itunes_image_link)
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
#
# If it's not present, falls back to the normal image link.
def media_thumbnail_link
if @media_thumbnail_link.nil?
# get the feed item itunes image link from the xml document
@media_thumbnail_link = XPath.first(root_node, "media:thumbnail/@url").to_s
@media_thumbnail_link = FeedTools.normalize_url(@media_thumbnail_link)
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?
unless root_node.nil?
repair_entities = false
copyright_node = XPath.first(root_node, "dc:rights")
if copyright_node.nil?
copyright_node = XPath.first(root_node, "dc:rights",
FEED_TOOLS_NAMESPACES)
end
if copyright_node.nil?
copyright_node = XPath.first(root_node, "rights",
FEED_TOOLS_NAMESPACES)
end
if copyright_node.nil?
copyright_node = XPath.first(root_node, "copyright",
FEED_TOOLS_NAMESPACES)
end
if copyright_node.nil?
copyright_node = XPath.first(root_node, "atom03:copyright",
FEED_TOOLS_NAMESPACES)
end
if copyright_node.nil?
copyright_node = XPath.first(root_node, "atom10:copyright",
FEED_TOOLS_NAMESPACES)
end
if copyright_node.nil?
copyright_node = XPath.first(root_node, "copyrights",
FEED_TOOLS_NAMESPACES)
end
end
if copyright_node.nil?
return nil
end
copyright_type = XPath.first(copyright_node, "@type").to_s
copyright_mode = XPath.first(copyright_node, "@mode").to_s
copyright_encoding = XPath.first(copyright_node, "@encoding").to_s
# Note that we're checking for misuse of type, mode and encoding here
if copyright_encoding != ""
@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 == ""
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 = XPath.match(root_node, "enclosure")
atom_enclosures = XPath.match(root_node, "link[@rel='enclosure']")
media_content_enclosures = XPath.match(root_node, "media:content")
media_group_enclosures = XPath.match(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")
if XPath.first(enclosure_node, "media:thumbnail/@url").to_s != ""
enclosure.thumbnail = EnclosureThumbnail.new(
FeedTools.unescape_entities(XPath.first(enclosure_node, "media:thumbnail/@url").to_s),
FeedTools.unescape_entities(XPath.first(enclosure_node, "media:thumbnail/@height").to_s),
FeedTools.unescape_entities(XPath.first(enclosure_node, "media:thumbnail/@width").to_s)
)
if enclosure.thumbnail.height == ""
enclosure.thumbnail.height = nil
end
if enclosure.thumbnail.width == ""
enclosure.thumbnail.width = nil
end
end
enclosure.categories = []
for category in XPath.match(enclosure_node, "media:category")
enclosure.categories << FeedTools::Feed::Category.new
enclosure.categories.last.term =
FeedTools.unescape_entities(category.text)
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 == ""
enclosure.categories.last.scheme = nil
end
if enclosure.categories.last.label == ""
enclosure.categories.last.label = nil
end
end
if XPath.first(enclosure_node, "media:hash/text()").to_s != ""
enclosure.hash = EnclosureHash.new(
FeedTools.sanitize_html(FeedTools.unescape_entities(XPath.first(
enclosure_node, "media:hash/text()").to_s), :strip),
"md5"
)
end
if XPath.first(enclosure_node, "media:player/@url").to_s != ""
enclosure.player = EnclosurePlayer.new(
FeedTools.unescape_entities(XPath.first(enclosure_node, "media:player/@url").to_s),
FeedTools.unescape_entities(XPath.first(enclosure_node, "media:player/@height").to_s),
FeedTools.unescape_entities(XPath.first(enclosure_node, "media:player/@width").to_s)
)
if enclosure.player.height == ""
enclosure.player.height = nil
end
if enclosure.player.width == ""
enclosure.player.width = nil
end
end
enclosure.credits = []
for credit in XPath.match(enclosure_node, "media:credit")
enclosure.credits << EnclosureCredit.new(
FeedTools.unescape_entities(credit.text),
FeedTools.unescape_entities(credit.attributes["role"].to_s.downcase)
)
if enclosure.credits.last.role == ""
enclosure.credits.last.role = nil
end
end
enclosure.explicit = (XPath.first(enclosure_node,
"media:adult/text()").to_s.downcase == "true")
if XPath.first(enclosure_node, "media:text/text()").to_s != ""
enclosure.text = FeedTools.unescape_entities(XPath.first(enclosure_node,
"media:text/text()").to_s)
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 =
XPath.match(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
if enclosure.thumbnail.nil? &&
XPath.first(media_group, "media:thumbnail/@url").to_s != ""
enclosure.thumbnail = EnclosureThumbnail.new(
FeedTools.unescape_entities(
XPath.first(media_group, "media:thumbnail/@url").to_s),
FeedTools.unescape_entities(
XPath.first(media_group, "media:thumbnail/@height").to_s),
FeedTools.unescape_entities(
XPath.first(media_group, "media:thumbnail/@width").to_s)
)
if enclosure.thumbnail.height == ""
enclosure.thumbnail.height = nil
end
if enclosure.thumbnail.width == ""
enclosure.thumbnail.width = nil
end
end
if (enclosure.categories.nil? || enclosure.categories.size == 0)
enclosure.categories = []
for category in XPath.match(media_group, "media:category")
enclosure.categories << FeedTools::Feed::Category.new
enclosure.categories.last.term =
FeedTools.unescape_entities(category.text)
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 == ""
enclosure.categories.last.scheme = nil
end
if enclosure.categories.last.label == ""
enclosure.categories.last.label = nil
end
end
end
if enclosure.hash.nil? &&
XPath.first(media_group, "media:hash/text()").to_s != ""
enclosure.hash = EnclosureHash.new(
FeedTools.unescape_entities(XPath.first(media_group, "media:hash/text()").to_s),
"md5"
)
end
if enclosure.player.nil? &&
XPath.first(media_group, "media:player/@url").to_s != ""
enclosure.player = EnclosurePlayer.new(
FeedTools.unescape_entities(XPath.first(media_group, "media:player/@url").to_s),
FeedTools.unescape_entities(XPath.first(media_group, "media:player/@height").to_s),
FeedTools.unescape_entities(XPath.first(media_group, "media:player/@width").to_s)
)
if enclosure.player.height == ""
enclosure.player.height = nil
end
if enclosure.player.width == ""
enclosure.player.width = nil
end
end
if enclosure.credits.nil? || enclosure.credits.size == 0
enclosure.credits = []
for credit in XPath.match(media_group, "media:credit")
enclosure.credits << EnclosureCredit.new(
FeedTools.unescape_entities(credit.text),
FeedTools.unescape_entities(credit.attributes["role"].to_s.downcase)
)
if enclosure.credits.last.role == ""
enclosure.credits.last.role = nil
end
end
end
if enclosure.explicit?.nil?
enclosure.explicit = (XPath.first(media_group,
"media:adult/text()").to_s.downcase == "true") ? true : false
end
if enclosure.text.nil? &&
XPath.first(media_group, "media:text/text()").to_s != ""
enclosure.text = FeedTools.sanitize_html(FeedTools.unescape_entities(
XPath.first(media_group, "media:text/text()").to_s), :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
for itunes_category in XPath.match(root_node, "itunes:category")
genre = "Podcasts"
category = itunes_category.attributes["text"].to_s
subcategory = XPath.first(itunes_category, "itunes:category/@text").to_s
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 == ""
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 == "" || enclosure.expression.nil?
enclosure.expression = "full"
end
# If an enclosure is missing the text field, fall back on the itunes:summary field
if enclosure.text.nil? || enclosure.text = ""
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
unless root_node.nil?
author_node = XPath.first(root_node, "atom10:author",
FEED_TOOLS_NAMESPACES)
if author_node.nil?
author_node = XPath.first(root_node, "atom03:author",
FEED_TOOLS_NAMESPACES)
end
if author_node.nil?
author_node = XPath.first(root_node, "atom:author")
end
if author_node.nil?
author_node = XPath.first(root_node, "author")
end
if author_node.nil?
author_node = XPath.first(root_node, "managingEditor")
end
if author_node.nil?
author_node = XPath.first(root_node, "dc:author",
FEED_TOOLS_NAMESPACES)
end
if author_node.nil?
author_node = XPath.first(root_node, "dc:author")
end
if author_node.nil?
author_node = XPath.first(root_node, "dc:creator",
FEED_TOOLS_NAMESPACES)
end
if author_node.nil?
author_node = XPath.first(root_node, "dc:creator")
end
end
unless author_node.nil?
@author.raw = FeedTools.unescape_entities(
XPath.first(author_node, "text()").to_s)
@author.raw = nil if @author.raw == ""
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)
author_raw_pair = raw_scan.first.reverse unless raw_scan.size == 0
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
@author.name = "" if @author.name.nil?
if @author.name == ""
@author.name = FeedTools.unescape_entities(
XPath.first(author_node, "name/text()").to_s)
end
if @author.name == ""
@author.name = FeedTools.unescape_entities(
XPath.first(author_node, "@name").to_s)
end
if @author.email == ""
@author.email = FeedTools.unescape_entities(
XPath.first(author_node, "email/text()").to_s)
end
if @author.email == ""
@author.email = FeedTools.unescape_entities(
XPath.first(author_node, "@email").to_s)
end
if @author.url == ""
@author.url = FeedTools.unescape_entities(
XPath.first(author_node, "url/text()").to_s)
end
if @author.url == ""
@author.url = FeedTools.unescape_entities(
XPath.first(author_node, "@url").to_s)
end
@author.name = nil if @author.name == ""
@author.raw = nil if @author.raw == ""
@author.email = nil if @author.email == ""
@author.url = nil if @author.url == ""
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(
XPath.first(root_node, "dc:publisher/text()").to_s)
if @publisher.raw == ""
@publisher.raw = FeedTools.unescape_entities(
XPath.first(root_node, "webMaster/text()").to_s)
end
unless @publisher.raw == ""
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 == ""
@publisher.raw = nil if @publisher.raw == ""
@publisher.email = nil if @publisher.email == ""
@publisher.url = nil if @publisher.url == ""
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(XPath.first(root_node,
"itunes:author/text()").to_s)
@itunes_author = feed.itunes_author if @itunes_author == ""
@itunes_author = nil if @itunes_author == ""
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(XPath.first(root_node,
"itunes:duration/text()").to_s)
if raw_duration != ""
hms = raw_duration.split(":").map { |x| x.to_i }
if hms.size == 3
@itunes_duration = hms[0].hour + hms[1].minute + hms[2]
elsif hms.size == 2
@itunes_duration = hms[0].minute + 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?
unless root_node.nil?
time_string = XPath.first(root_node, "pubDate/text()").to_s
if time_string == ""
time_string = XPath.first(root_node, "dc:date/text()").to_s
end
if time_string == ""
time_string = XPath.first(root_node, "issued/text()").to_s
end
if time_string == ""
time_string = XPath.first(root_node, "updated/text()").to_s
end
if time_string == ""
time_string = XPath.first(root_node, "time/text()").to_s
end
end
begin
time_string = "" if time_string.nil?
if time_string != ""
@time = Time.parse(time_string).gmtime
end
rescue
end
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
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("@items").nil?
parent_feed.items
end
unsorted_items = parent_feed.instance_variable_get("@items")
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("@items").nil?
parent_feed.items
end
unsorted_items = parent_feed.instance_variable_get("@items")
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?
unless root_node.nil?
updated_string = XPath.first(root_node, "updated/text()").to_s
if updated_string == ""
updated_string = XPath.first(root_node, "modified/text()").to_s
end
end
if updated_string != nil && updated_string != ""
@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 issued time
def issued
if @issued.nil?
unless root_node.nil?
issued_string = XPath.first(root_node, "issued/text()").to_s
if issued_string == ""
issued_string = XPath.first(root_node, "published/text()").to_s
end
if issued_string == ""
issued_string = XPath.first(root_node, "pubDate/text()").to_s
end
if issued_string == ""
issued_string = XPath.first(root_node, "dc:date/text()").to_s
end
end
if issued_string != nil && issued_string != ""
@issued = Time.parse(issued_string).gmtime rescue nil
else
@issued = nil
end
end
return @issued
end
# Sets the feed item issued time
def issued=(new_issued)
@issued = new_issued
end
# Returns the url for posting comments
def comments
if @comments.nil?
@comments = FeedTools.normalize_url(
XPath.first(root_node, "comments/text()").to_s)
@comments = nil if @comments == ""
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 = XPath.first(root_node, "source/@url").to_s
@source.url = nil if @source.url == ""
@source.value = XPath.first(root_node, "source/text()").to_s
@source.value = nil if @source.value == ""
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 = XPath.match(root_node, "dc:subject/rdf:Bag/rdf:li/text()")
if tag_list.nil? || tag_list.size == 0
tag_list = XPath.match(root_node,
"dc:subject/rdf:Bag/rdf:li/text()", FEED_TOOLS_NAMESPACES)
end
if tag_list != nil && tag_list.size > 1
for tag in tag_list
@tags << tag.to_s.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 = XPath.match(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 = XPath.first(root_node, "@resource").to_s
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 = XPath.match(root_node, "category/text()")
for tag in tag_list
@tags << tag.to_s.downcase.strip
end
end
if @tags.nil? || @tags.size == 0
@tags = []
tag_list = XPath.match(root_node, "dc:subject/text()")
for tag in tag_list
@tags << tag.to_s.downcase.strip
end
end
if @tags.nil? || @tags.size == 0
begin
@tags = XPath.first(root_node, "itunes:keywords/text()").to_s.downcase.split(" ")
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?
if XPath.first(root_node,
"media:adult/text()").to_s.downcase == "true" ||
XPath.first(root_node,
"itunes:explicit/text()").to_s.downcase == "yes" ||
XPath.first(root_node,
"itunes:explicit/text()").to_s.downcase == "true" ||
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 "rss"), version=nil,
xml_builder=Builder::XmlMarkup.new(:indent => 2))
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" => CGI.escapeHTML(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
xml_builder.tag!("itunes:keywords", tags.join(" "))
end
build_xml_hook(feed_type, version, xml_builder)
end
elsif feed_type == "rss"
# normal rss format
return xml_builder.item do
unless title.nil? || title == ""
xml_builder.title(title)
end
unless link.nil? || link == ""
xml_builder.link(link)
end
unless description.nil? || description == ""
xml_builder.description(description)
end
unless time.nil?
xml_builder.pubDate(time.rfc822)
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
xml_builder.tag!("itunes:keywords", tags.join(" "))
end
build_xml_hook(feed_type, version, xml_builder)
end
elsif feed_type == "atom" && version == 0.3
# normal atom format
return xml_builder.entry("xmlns" =>
FEED_TOOLS_NAMESPACES['atom03']) do
unless title.nil? || title == ""
xml_builder.title(title,
"mode" => "escaped",
"type" => "text/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.url(self.author.url)
end
end
unless link.nil? || link == ""
xml_builder.link("href" => link,
"rel" => "alternate",
"type" => "text/html",
"title" => title)
end
unless description.nil? || description == ""
xml_builder.content(description,
"mode" => "escaped",
"type" => "text/html")
end
unless time.nil?
xml_builder.issued(time.iso8601)
end
unless tags.nil? || tags.size == 0
for tag in tags
xml_builder.category(tag)
end
end
build_xml_hook(feed_type, version, xml_builder)
end
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.url(self.author.url)
end
end
unless link.nil? || link == ""
xml_builder.link("href" => link,
"rel" => "alternate",
"type" => "text/html",
"title" => title)
end
unless description.nil? || description == ""
xml_builder.content(description,
"type" => "html")
else
xml_builder.content(FeedTools.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
build_xml_hook(feed_type, version, xml_builder)
end
end
end
alias_method :tagline, :description
alias_method :tagline=, :description=
alias_method :subtitle, :description
alias_method :subtitle=, :description=
alias_method :summary, :description
alias_method :summary=, :description=
alias_method :abstract, :description
alias_method :abstract=, :description=
alias_method :content, :description
alias_method :content=, :description=
alias_method :guid, :id
alias_method :guid=, :id=
alias_method :published, :issued
alias_method :published=, :issued=
# Returns a simple representation of the feed item object's state.
def inspect
return "#"
end
end
end