#-- # 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. #++ require 'feed_tools/feed_structures' module FeedTools # The FeedTools::FeedItem class represents the structure of # a single item within a web feed. class FeedItem # Initialize the feed object def initialize super @feed_data = nil @feed_data_type = :xml @xml_document = 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 load options for this feed. def configurations if @configurations.blank? parent_feed = self.feed if parent_feed != nil @configurations = parent_feed.configurations.dup else @configurations = FeedTools.configurations.dup end end return @configurations end # Sets the load options for this feed. def configurations=(new_configurations) @configurations = new_configurations end # Returns the feed item's encoding. def encoding if @encoding.nil? @encoding = self.feed.encoding end return @encoding 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_document if self.feed_data_type != :xml @xml_document = nil else if @xml_document.nil? # TODO: :ignore_whitespace_nodes => :all # Add that? # ====================================== @xml_document = REXML::Document.new(self.feed_data) end end return @xml_document 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 FeedTools::XmlHelper.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 FeedTools::XmlHelper.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 self.xml_document.nil? return nil end @root_node = self.xml_document.root end return @root_node end # Sets the root node of the feed item. # # This allows namespace information to be inherited by the feed item # from the feed itself. When creating individual nodes from scratch, # the feed_data= method should be used instead. def root_node=(new_root_node) @root_node = new_root_node end # Returns the feed type of this item def feed_type if @feed_type.nil? parent_feed = self.feed @feed_type = parent_feed.feed_type unless parent_feed.nil? end return @feed_type end # Returns the feed version of this item def feed_version if @feed_version.nil? parent_feed = self.feed @feed_version = parent_feed.feed_version unless parent_feed.nil? end return @feed_version end # Returns the feed items's unique id def id if @id.nil? @id = FeedTools::XmlHelper.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 = FeedTools::XmlHelper.try_xpaths(self.root_node, [ "atom10:title", "atom03:title", "atom:title", "title", "dc:title" ]) @title = FeedTools::HtmlHelper.process_text_construct(title_node, self.feed_type, self.feed_version) if self.feed_type == "atom" || self.configurations[:always_strip_wrapper_elements] @title = FeedTools::HtmlHelper.strip_wrapper_element(@title) end if !@title.blank? && self.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 = 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 = FeedTools::XmlHelper.try_xpaths(self.root_node, [ "atom10:content", "atom03:content", "atom:content", "xhtml:body", "body", "xhtml:div", "div", "p:payload", "payload", "content:encoded", "content", "fullitem", "encoded", "description", "tagline", "subtitle", "atom10:summary", "atom03:summary", "atom:summary", "summary", "abstract", "blurb", "info" ]) @content = FeedTools::HtmlHelper.process_text_construct(content_node, self.feed_type, self.feed_version) if self.feed_type == "atom" || self.configurations[:always_strip_wrapper_elements] @content = FeedTools::HtmlHelper.strip_wrapper_element(@content) end if @content.blank? @content = self.media_text end if @content.blank? @content = self.itunes_summary end if @content.blank? @content = self.itunes_subtitle end end return @content end # Sets the feed item content def content=(new_content) @content = new_content end # Returns the feed item summary def summary if @summary.nil? repair_entities = false summary_node = FeedTools::XmlHelper.try_xpaths(self.root_node, [ "atom10:summary", "atom03:summary", "atom:summary", "summary", "abstract", "blurb", "description", "tagline", "subtitle", "xhtml:body", "body", "xhtml:div", "div", "p:payload", "payload", "fullitem", "content:encoded", "encoded", "atom10:content", "atom03:content", "atom:content", "content", "info" ]) @summary = FeedTools::HtmlHelper.process_text_construct(summary_node, self.feed_type, self.feed_version) if self.feed_type == "atom" || self.configurations[:always_strip_wrapper_elements] @summary = FeedTools::HtmlHelper.strip_wrapper_element(@summary) end if @summary.blank? @summary = self.media_text end if @summary.blank? @summary = self.itunes_summary end if @summary.blank? @summary = self.itunes_subtitle end end return @summary end # Sets the feed item summary def summary=(new_summary) @summary = new_summary end # Returns the contents of the itunes:summary element def itunes_summary if @itunes_summary.nil? @itunes_summary = FeedTools::XmlHelper.try_xpaths(self.root_node, [ "itunes:summary/text()" ], :select_result_value => true) unless @itunes_summary.blank? @itunes_summary = FeedTools::HtmlHelper.unescape_entities(@itunes_summary) @itunes_summary = FeedTools::HtmlHelper.sanitize_html(@itunes_summary) @itunes_summary.strip! 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 = FeedTools::XmlHelper.try_xpaths(self.root_node, [ "itunes:subtitle/text()" ], :select_result_value => true) unless @itunes_subtitle.blank? @itunes_subtitle = FeedTools::HtmlHelper.unescape_entities(@itunes_subtitle) @itunes_subtitle = FeedTools::HtmlHelper.sanitize_html(@itunes_subtitle) @itunes_subtitle.strip! 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::XmlHelper.try_xpaths(self.root_node, [ "media:text/text()" ], :select_result_value => true) unless @media_text.blank? @media_text = FeedTools::HtmlHelper.unescape_entities(@media_text) @media_text = FeedTools::HtmlHelper.sanitize_html(@media_text) @media_text.strip! else @media_text = nil 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? max_score = 0 for link_object in self.links.reverse score = 0 if FeedTools::HtmlHelper.html_type?(link_object.type) score = score + 2 elsif link_object.type != nil score = score - 1 end if FeedTools::HtmlHelper.xml_type?(link_object.type) score = score + 1 end if link_object.rel == "alternate" score = score + 1 end if link_object.rel == "self" score = score - 1 end if score >= max_score max_score = score @link = link_object.href end end if @link.blank? @link = FeedTools::XmlHelper.try_xpaths(self.root_node, [ "@href", "@rdf:about", "@about" ], :select_result_value => true) end if @link.blank? if FeedTools::UriHelper.is_uri?(self.id) && (self.id =~ /^http/) @link = self.id end end if !@link.blank? @link = FeedTools::HtmlHelper.unescape_entities(@link) end @link = self.comments if @link.blank? @link = nil if @link.blank? begin if !(@link =~ /^file:/) && !FeedTools::UriHelper.is_uri?(@link) stored_base_uri = FeedTools::GenericHelper.recursion_trap(:feed_link) do self.feed.base_uri if self.feed != nil end root_base_uri = nil unless self.root_node.nil? root_base_uri = self.root_node.base_uri end @link = FeedTools::UriHelper.resolve_relative_uri( @link, [root_base_uri,stored_base_uri]) end rescue end if self.configurations[:url_normalization_enabled] @link = FeedTools::UriHelper.normalize_url(@link) end end return @link end # Sets the feed item link def link=(new_link) @link = new_link end # Returns the links collection def links if @links.nil? @links = [] link_nodes = FeedTools::XmlHelper.combine_xpaths_all(self.root_node, [ "atom10:link", "atom03:link", "atom:link", "link", "a", "url", "href" ]) for link_node in link_nodes link_object = FeedTools::Link.new link_object.href = FeedTools::XmlHelper.try_xpaths(link_node, [ "@atom10:href", "@atom03:href", "@atom:href", "@href", "text()" ], :select_result_value => true) if link_object.href.nil? && link_node.base_uri != nil link_object.href = "" end begin if !(link_object.href =~ /^file:/) && !FeedTools::UriHelper.is_uri?(link_object.href) stored_base_uri = FeedTools::GenericHelper.recursion_trap(:feed_link) do self.feed.base_uri if self.feed != nil end link_object.href = FeedTools::UriHelper.resolve_relative_uri( link_object.href, [link_node.base_uri, stored_base_uri]) end rescue end if self.configurations[:url_normalization_enabled] link_object.href = FeedTools::UriHelper.normalize_url(link_object.href) end link_object.href.strip! unless link_object.href.nil? next if link_object.href.blank? link_object.hreflang = FeedTools::XmlHelper.try_xpaths(link_node, [ "@atom10:hreflang", "@atom03:hreflang", "@atom:hreflang", "@hreflang" ], :select_result_value => true) unless link_object.hreflang.nil? link_object.hreflang = link_object.hreflang.downcase end link_object.rel = FeedTools::XmlHelper.try_xpaths(link_node, [ "@atom10:rel", "@atom03:rel", "@atom:rel", "@rel" ], :select_result_value => true) unless link_object.rel.nil? link_object.rel = link_object.rel.downcase end link_object.type = FeedTools::XmlHelper.try_xpaths(link_node, [ "@atom10:type", "@atom03:type", "@atom:type", "@type" ], :select_result_value => true) unless link_object.type.nil? link_object.type = link_object.type.downcase end link_object.title = FeedTools::XmlHelper.try_xpaths(link_node, [ "@atom10:title", "@atom03:title", "@atom:title", "@title", "text()" ], :select_result_value => true) # This catches the ambiguities between atom, rss, and cdf if link_object.title == link_object.href link_object.title = nil end link_object.length = FeedTools::XmlHelper.try_xpaths(link_node, [ "@atom10:length", "@atom03:length", "@atom:length", "@length" ], :select_result_value => true) if !link_object.length.nil? link_object.length = link_object.length.to_i else if !link_object.type.nil? && link_object.type[0..4] != "text" && link_object.type[-3..-1] != "xml" && link_object.href =~ /^http:\/\// # Retrieve the length with an http HEAD request else link_object.length = nil end end @links << link_object end end return @links end # Sets the links collection def links=(new_links) @links = new_links end # Returns a list of the feed item's categories def categories if @categories.nil? @categories = [] category_nodes = FeedTools::XmlHelper.try_xpaths_all(self.root_node, [ "category", "dc:subject" ]) for category_node in category_nodes category = FeedTools::Category.new category.term = FeedTools::XmlHelper.try_xpaths(category_node, ["@term", "text()"], :select_result_value => true) category.term.strip! unless category.term.nil? category.label = FeedTools::XmlHelper.try_xpaths(category_node, ["@label"], :select_result_value => true) category.label.strip! unless category.label.nil? category.scheme = FeedTools::XmlHelper.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 = FeedTools::XmlHelper.try_xpaths_all(self.root_node, [ "image", "logo", "apple-wallpapers:image", "imageUrl" ]) unless image_nodes.blank? for image_node in image_nodes image = FeedTools::Image.new image.href = FeedTools::XmlHelper.try_xpaths(image_node, [ "url/text()", "@rdf:resource", "@href", "text()" ], :select_result_value => true) if image.href.nil? && image_node.base_uri != nil image.href = "" end begin if !(image.href =~ /^file:/) && !FeedTools::UriHelper.is_uri?(image.href) stored_base_uri = FeedTools::GenericHelper.recursion_trap(:feed_link) do self.feed.base_uri if self.feed != nil end image.href = FeedTools::UriHelper.resolve_relative_uri( image.href, [image_node.base_uri, stored_base_uri]) end rescue end if self.configurations[:url_normalization_enabled] image.href = FeedTools::UriHelper.normalize_url(image.href) end image.href.strip! unless image.href.nil? next if image.href.blank? image.title = FeedTools::XmlHelper.try_xpaths(image_node, ["title/text()"], :select_result_value => true) image.title.strip! unless image.title.nil? image.description = FeedTools::XmlHelper.try_xpaths(image_node, ["description/text()"], :select_result_value => true) image.description.strip! unless image.description.nil? image.link = FeedTools::XmlHelper.try_xpaths(image_node, ["link/text()"], :select_result_value => true) image.link.strip! unless image.link.nil? image.height = FeedTools::XmlHelper.try_xpaths(image_node, ["height/text()"], :select_result_value => true).to_i image.height = nil if image.height <= 0 image.width = FeedTools::XmlHelper.try_xpaths(image_node, ["width/text()"], :select_result_value => true).to_i image.width = nil if image.width <= 0 image.style = FeedTools::XmlHelper.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 for link_object in self.links if link_object.type != nil && link_object.type =~ /^image/ image = FeedTools::Image.new image.href = link_object.href image.title = link_object.title @images << image unless image.href.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 = FeedTools::XmlHelper.try_xpaths(self.root_node, [ "itunes:image/@href", "itunes:link[@rel='image']/@href" ], :select_result_value => true) if self.configurations[:url_normalization_enabled] @itunes_image_link = FeedTools::UriHelper.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 = FeedTools::XmlHelper.try_xpaths(self.root_node, [ "media:thumbnail/@url" ], :select_result_value => true) if self.configurations[:url_normalization_enabled] @media_thumbnail_link = FeedTools::UriHelper.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 rights information def rights if @rights.nil? repair_entities = false rights_node = FeedTools::XmlHelper.try_xpaths(self.root_node, [ "atom10:copyright", "atom03:copyright", "atom:copyright", "copyright", "copyrights", "dc:rights", "rights" ]) @rights = FeedTools::HtmlHelper.process_text_construct(rights_node, self.feed_type, self.feed_version) if self.feed_type == "atom" || self.configurations[:always_strip_wrapper_elements] @rights = FeedTools::HtmlHelper.strip_wrapper_element(@rights) end end return @rights end # Sets the feed item's rights information def rights=(new_rights) @rights = new_rights end def license #:nodoc: raise "Not implemented yet." end def license=(new_license) #:nodoc: raise "Not implemented yet." end # Returns all feed item enclosures def enclosures if @enclosures.nil? @enclosures = [] # First, load up all the different possible sources of enclosures rss_enclosures = FeedTools::XmlHelper.try_xpaths_all(self.root_node, ["enclosure"]) atom_enclosures = FeedTools::XmlHelper.try_xpaths_all(self.root_node, [ "atom10:link[@rel='enclosure']", "atom03:link[@rel='enclosure']", "atom:link[@rel='enclosure']", "link[@rel='enclosure']" ]) media_content_enclosures = FeedTools::XmlHelper.try_xpaths_all(self.root_node, ["media:content"]) media_group_enclosures = FeedTools::XmlHelper.try_xpaths_all(self.root_node, ["media:group"]) # TODO: Implement this bittorrent_enclosures = FeedTools::XmlHelper.try_xpaths_all(self.root_node, ["bitTorrent:torrent"]) # 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 = FeedTools::Enclosure.new enclosure.url = FeedTools::HtmlHelper.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::HtmlHelper.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 = FeedTools::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::HtmlHelper.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 = FeedTools::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 = FeedTools::XmlHelper.try_xpaths(enclosure_node, ["media:thumbnail/@url"], :select_result_value => true) if !enclosure_thumbnail_url.blank? enclosure.thumbnail = FeedTools::EnclosureThumbnail.new( FeedTools::HtmlHelper.unescape_entities(enclosure_thumbnail_url), FeedTools::HtmlHelper.unescape_entities( FeedTools::XmlHelper.try_xpaths(enclosure_node, ["media:thumbnail/@height"], :select_result_value => true)), FeedTools::HtmlHelper.unescape_entities( FeedTools::XmlHelper.try_xpaths(enclosure_node, ["media:thumbnail/@width"], :select_result_value => true)) ) end enclosure.categories = [] for category in FeedTools::XmlHelper.try_xpaths_all(enclosure_node, ["media:category"]) enclosure.categories << FeedTools::Category.new enclosure.categories.last.term = FeedTools::HtmlHelper.unescape_entities(category.inner_xml) enclosure.categories.last.scheme = FeedTools::HtmlHelper.unescape_entities( category.attributes["scheme"].to_s) enclosure.categories.last.label = FeedTools::HtmlHelper.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 = FeedTools::XmlHelper.try_xpaths(enclosure_node, ["media:hash/text()"], :select_result_value => true) if !enclosure_media_hash.nil? enclosure.hash = FeedTools::EnclosureHash.new( FeedTools::HtmlHelper.sanitize_html(FeedTools::HtmlHelper.unescape_entities( enclosure_media_hash), :strip), "md5" ) end enclosure_media_player_url = FeedTools::XmlHelper.try_xpaths(enclosure_node, ["media:player/@url"], :select_result_value => true) if !enclosure_media_player_url.blank? enclosure.player = FeedTools::EnclosurePlayer.new( FeedTools::HtmlHelper.unescape_entities(enclosure_media_player_url), FeedTools::HtmlHelper.unescape_entities( FeedTools::XmlHelper.try_xpaths(enclosure_node, ["media:player/@height"], :select_result_value => true)), FeedTools::HtmlHelper.unescape_entities( FeedTools::XmlHelper.try_xpaths(enclosure_node, ["media:player/@width"], :select_result_value => true)) ) end enclosure.credits = [] for credit in FeedTools::XmlHelper.try_xpaths_all(enclosure_node, ["media:credit"]) enclosure.credits << FeedTools::EnclosureCredit.new( FeedTools::HtmlHelper.unescape_entities(credit.inner_xml.to_s.strip), FeedTools::HtmlHelper.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 = (FeedTools::XmlHelper.try_xpaths(enclosure_node, ["media:adult/text()"]).to_s.downcase == "true") enclosure_media_text = FeedTools::XmlHelper.try_xpaths(enclosure_node, ["media:text/text()"]) if !enclosure_media_text.blank? enclosure.text = FeedTools::HtmlHelper.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 = FeedTools::XmlHelper.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 = FeedTools::XmlHelper.try_xpaths(media_group, ["media:thumbnail/@url"], :select_result_value => true) if enclosure.thumbnail.nil? && !media_group_thumbnail.blank? enclosure.thumbnail = FeedTools::EnclosureThumbnail.new( FeedTools::HtmlHelper.unescape_entities( media_group_thumbnail), FeedTools::HtmlHelper.unescape_entities( FeedTools::XmlHelper.try_xpaths(media_group, ["media:thumbnail/@height"], :select_result_value => true)), FeedTools::HtmlHelper.unescape_entities( FeedTools::XmlHelper.try_xpaths(media_group, ["media:thumbnail/@width"], :select_result_value => true)) ) end if (enclosure.categories.blank?) enclosure.categories = [] for category in FeedTools::XmlHelper.try_xpaths_all(media_group, ["media:category"]) enclosure.categories << FeedTools::Category.new enclosure.categories.last.term = FeedTools::HtmlHelper.unescape_entities(category.inner_xml) enclosure.categories.last.scheme = FeedTools::HtmlHelper.unescape_entities( category.attributes["scheme"].to_s) enclosure.categories.last.label = FeedTools::HtmlHelper.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 = FeedTools::XmlHelper.try_xpaths(enclosure_node, ["media:hash/text()"], :select_result_value => true) if enclosure.hash.nil? && !enclosure_media_group_hash.blank? enclosure.hash = FeedTools::EnclosureHash.new( FeedTools::HtmlHelper.sanitize_html(FeedTools::HtmlHelper.unescape_entities( enclosure_media_group_hash), :strip), "md5" ) end enclosure_media_group_url = FeedTools::XmlHelper.try_xpaths(media_group, "media:player/@url", :select_result_value => true) if enclosure.player.nil? && !enclosure_media_group_url.blank? enclosure.player = FeedTools::EnclosurePlayer.new( FeedTools::HtmlHelper.unescape_entities(enclosure_media_group_url), FeedTools::HtmlHelper.unescape_entities( FeedTools::XmlHelper.try_xpaths(media_group, ["media:player/@height"], :select_result_value => true)), FeedTools::HtmlHelper.unescape_entities( FeedTools::XmlHelper.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 FeedTools::XmlHelper.try_xpaths_all(media_group, ["media:credit"]) enclosure.credits << FeedTools::EnclosureCredit.new( FeedTools::HtmlHelper.unescape_entities(credit.inner_xml), FeedTools::HtmlHelper.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 = ((FeedTools::XmlHelper.try_xpaths(media_group, [ "media:adult/text()" ], :select_result_value => true).downcase == "true") ? true : false) end enclosure_media_group_text = FeedTools::XmlHelper.try_xpaths(media_group, ["media:text/text()"], :select_result_value => true) if enclosure.text.nil? && !enclosure_media_group_text.blank? enclosure.text = FeedTools::HtmlHelper.sanitize_html( FeedTools::HtmlHelper.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 = FeedTools::XmlHelper.try_xpaths_all(self.root_node, ["itunes:category"]) for itunes_category in itunes_categories genre = "Podcasts" category = itunes_category.attributes["text"].to_s subcategory = FeedTools::XmlHelper.try_xpaths(itunes_category, ["itunes:category/@text"], :select_result_value => true) category_path = genre if !category.blank? category_path << "/" + category end if !subcategory.blank? category_path << "/" + subcategory end for enclosure in @enclosures if enclosure.categories.nil? enclosure.categories = [] end enclosure.categories << FeedTools::Category.new enclosure.categories.last.term = FeedTools::HtmlHelper.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::Author.new author_node = FeedTools::XmlHelper.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::XmlHelper.try_xpaths( author_node, ["text()"], :select_result_value => true) @author.raw = FeedTools::HtmlHelper.unescape_entities(@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) 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::HtmlHelper.unescape_entities( FeedTools::XmlHelper.try_xpaths(author_node, [ "atom10:name/text()", "atom03:name/text()", "atom:name/text()", "name/text()", "@name" ], :select_result_value => true) ) end if @author.email.blank? @author.email = FeedTools::HtmlHelper.unescape_entities( FeedTools::XmlHelper.try_xpaths(author_node, [ "atom10:email/text()", "atom03:email/text()", "atom:email/text()", "email/text()", "@email" ], :select_result_value => true) ) end if @author.url.blank? @author.url = FeedTools::HtmlHelper.unescape_entities( FeedTools::XmlHelper.try_xpaths(author_node, [ "atom10:url/text()", "atom03:url/text()", "atom:url/text()", "url/text()", "atom10:uri/text()", "atom03:uri/text()", "atom:uri/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? if @author.url != nil begin if !(@author.url =~ /^file:/) && !FeedTools::UriHelper.is_uri?(@author.url) @author.url = FeedTools::UriHelper.resolve_relative_uri( @author.url, [author_node.base_uri, self.base_uri]) end rescue end end 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::Author.new end @author.name = new_author end end # Returns the feed publisher def publisher if @publisher.nil? @publisher = FeedTools::Author.new # Set the author name @publisher.raw = FeedTools::HtmlHelper.unescape_entities( FeedTools::XmlHelper.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? if @publisher.url != nil begin if !(@publisher.url =~ /^file:/) && !FeedTools::UriHelper.is_uri?(@publisher.url) root_base_uri = nil unless self.root_node.nil? root_base_uri = self.root_node.base_uri end @publisher.url = FeedTools::UriHelper.resolve_relative_uri( @publisher.url, [root_base_uri, self.base_uri]) end rescue end end 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::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::HtmlHelper.unescape_entities( FeedTools::XmlHelper.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::HtmlHelper.unescape_entities( FeedTools::XmlHelper.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 = {}) FeedTools::GenericHelper.validate_options([ :estimate_timestamp ], options.keys) options = { :estimate_timestamp => true }.merge(options) if @time.nil? time_string = FeedTools::XmlHelper.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()", "lastBuildDate/text()", "atom10:issued/text()", "atom03:issued/text()", "atom:issued/text()", "issued/text()", "atom10:published/text()", "atom03:published/text()", "atom:published/text()", "published/text()", "dc:date/text()", "pubDate/text()", "date/text()" ], :select_result_value => true) begin if !time_string.blank? @time = Time.parse(time_string).gmtime elsif self.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 self.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 = FeedTools::XmlHelper.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()", "lastBuildDate/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 = FeedTools::XmlHelper.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()", "dc:date/text()", "pubDate/text()", "date/text()" ], :select_result_value => true) if !published_string.blank? @published = Time.parse(published_string).gmtime rescue nil else @published = nil end end return @published 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 = FeedTools::XmlHelper.try_xpaths(self.root_node, ["comments/text()"], :select_result_value => true) begin if !(@comments =~ /^file:/) && !FeedTools::UriHelper.is_uri?(@comments) root_base_uri = nil unless self.root_node.nil? root_base_uri = self.root_node.base_uri end @comments = FeedTools::UriHelper.resolve_relative_uri( @comments, [root_base_uri, self.base_uri]) end rescue end if self.configurations[:url_normalization_enabled] @comments = FeedTools::UriHelper.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::Link.new @source.href = FeedTools::XmlHelper.try_xpaths( self.root_node, ["source/@url"], :select_result_value => true) @source.title = FeedTools::XmlHelper.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 = FeedTools::XmlHelper.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 = FeedTools::XmlHelper.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 = FeedTools::XmlHelper.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 = FeedTools::XmlHelper.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 = FeedTools::XmlHelper.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 = FeedTools::XmlHelper.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 = FeedTools::XmlHelper.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::HtmlHelper.escape_entities(link)) do unless self.title.blank? xml_builder.title(FeedTools::HtmlHelper.strip_html_tags(self.title)) else xml_builder.title end unless self.link.blank? xml_builder.link(self.link) else xml_builder.link end unless self.author.nil? || self.author.name.nil? xml_builder.tag!("dc:creator", self.author.name) end unless self.summary.blank? xml_builder.description(self.summary) else xml_builder.description end unless self.content.blank? xml_builder.tag!("content:encoded") do xml_builder.cdata!(self.content) end end unless time.nil? xml_builder.tag!("dc:date", time.iso8601) end unless self.rights.blank? xml_builder.tag!("dc:rights", self.rights) end unless tags.nil? || tags.size == 0 for tag in tags xml_builder.tag!("dc:subject", tag) 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(FeedTools::HtmlHelper.strip_html_tags(self.title)) end unless self.link.blank? xml_builder.link(link) end unless self.author.nil? || self.author.name.nil? xml_builder.tag!("dc:creator", self.author.name) end unless self.summary.blank? xml_builder.description(self.summary) end unless self.content.blank? xml_builder.tag!("content:encoded") do xml_builder.cdata!(self.content) end end if !self.published.nil? xml_builder.pubDate(self.published.rfc822) elsif !self.time.nil? xml_builder.pubDate(self.time.rfc822) end unless self.copyright.blank? xml_builder.tag!("dc:rights", self.copyright) end unless self.guid.blank? if FeedTools::UriHelper.is_uri?(self.guid) && (self.guid =~ /^http/) 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 for tag in tags xml_builder.tag!("category", tag) 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::UriHelper.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( FeedTools::HtmlHelper.strip_html_tags(self.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::HtmlHelper.escape_entities(self.link), "rel" => "alternate") end if !self.content.blank? xml_builder.content(self.content, "type" => "html") end if !self.summary.blank? xml_builder.summary(self.summary, "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 unless self.rights.blank? xml_builder.rights(self.rights) end if self.id != nil unless FeedTools::UriHelper.is_uri? self.id if self.time != nil && self.link != nil xml_builder.id(FeedTools::UriHelper.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::UriHelper.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::UriHelper.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 :abstract, :summary alias_method :abstract=, :summary= alias_method :description, :summary alias_method :description=, :summary= alias_method :copyright, :rights alias_method :copyright=, :rights= 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