summaryrefslogtreecommitdiff
path: root/trunk/lib/rss/maker/base.rb
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/lib/rss/maker/base.rb')
-rw-r--r--trunk/lib/rss/maker/base.rb880
1 files changed, 880 insertions, 0 deletions
diff --git a/trunk/lib/rss/maker/base.rb b/trunk/lib/rss/maker/base.rb
new file mode 100644
index 0000000000..2262a764ec
--- /dev/null
+++ b/trunk/lib/rss/maker/base.rb
@@ -0,0 +1,880 @@
+require 'forwardable'
+
+require 'rss/rss'
+
+module RSS
+ module Maker
+ class Base
+ extend Utils::InheritedReader
+
+ OTHER_ELEMENTS = []
+ NEED_INITIALIZE_VARIABLES = []
+
+ class << self
+ def other_elements
+ inherited_array_reader("OTHER_ELEMENTS")
+ end
+ def need_initialize_variables
+ inherited_array_reader("NEED_INITIALIZE_VARIABLES")
+ end
+
+ def inherited_base
+ ::RSS::Maker::Base
+ end
+
+ def inherited(subclass)
+ subclass.const_set("OTHER_ELEMENTS", [])
+ subclass.const_set("NEED_INITIALIZE_VARIABLES", [])
+ end
+
+ def add_other_element(variable_name)
+ self::OTHER_ELEMENTS << variable_name
+ end
+
+ def add_need_initialize_variable(variable_name, init_value=nil,
+ &init_block)
+ init_value ||= init_block
+ self::NEED_INITIALIZE_VARIABLES << [variable_name, init_value]
+ end
+
+ def def_array_element(name, plural=nil, klass_name=nil)
+ include Enumerable
+ extend Forwardable
+
+ plural ||= "#{name}s"
+ klass_name ||= Utils.to_class_name(name)
+ def_delegators("@#{plural}", :<<, :[], :[]=, :first, :last)
+ def_delegators("@#{plural}", :push, :pop, :shift, :unshift)
+ def_delegators("@#{plural}", :each, :size, :empty?, :clear)
+
+ add_need_initialize_variable(plural) {[]}
+
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ def new_#{name}
+ #{name} = self.class::#{klass_name}.new(@maker)
+ @#{plural} << #{name}
+ if block_given?
+ yield #{name}
+ else
+ #{name}
+ end
+ end
+ alias new_child new_#{name}
+
+ def to_feed(*args)
+ @#{plural}.each do |#{name}|
+ #{name}.to_feed(*args)
+ end
+ end
+
+ def replace(elements)
+ @#{plural}.replace(elements.to_a)
+ end
+ EOC
+ end
+
+ def def_classed_element_without_accessor(name, class_name=nil)
+ class_name ||= Utils.to_class_name(name)
+ add_other_element(name)
+ add_need_initialize_variable(name) do |object|
+ object.send("make_#{name}")
+ end
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ private
+ def setup_#{name}(feed, current)
+ @#{name}.to_feed(feed, current)
+ end
+
+ def make_#{name}
+ self.class::#{class_name}.new(@maker)
+ end
+ EOC
+ end
+
+ def def_classed_element(name, class_name=nil, attribute_name=nil)
+ def_classed_element_without_accessor(name, class_name)
+ if attribute_name
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ def #{name}
+ if block_given?
+ yield(@#{name})
+ else
+ @#{name}.#{attribute_name}
+ end
+ end
+
+ def #{name}=(new_value)
+ @#{name}.#{attribute_name} = new_value
+ end
+ EOC
+ else
+ attr_reader name
+ end
+ end
+
+ def def_classed_elements(name, attribute, plural_class_name=nil,
+ plural_name=nil, new_name=nil)
+ plural_name ||= "#{name}s"
+ new_name ||= name
+ def_classed_element(plural_name, plural_class_name)
+ local_variable_name = "_#{name}"
+ new_value_variable_name = "new_value"
+ additional_setup_code = nil
+ if block_given?
+ additional_setup_code = yield(local_variable_name,
+ new_value_variable_name)
+ end
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ def #{name}
+ #{local_variable_name} = #{plural_name}.first
+ #{local_variable_name} ? #{local_variable_name}.#{attribute} : nil
+ end
+
+ def #{name}=(#{new_value_variable_name})
+ #{local_variable_name} =
+ #{plural_name}.first || #{plural_name}.new_#{new_name}
+ #{additional_setup_code}
+ #{local_variable_name}.#{attribute} = #{new_value_variable_name}
+ end
+ EOC
+ end
+
+ def def_other_element(name)
+ attr_accessor name
+ def_other_element_without_accessor(name)
+ end
+
+ def def_other_element_without_accessor(name)
+ add_need_initialize_variable(name)
+ add_other_element(name)
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ def setup_#{name}(feed, current)
+ if !@#{name}.nil? and current.respond_to?(:#{name}=)
+ current.#{name} = @#{name}
+ end
+ end
+ EOC
+ end
+
+ def def_csv_element(name, type=nil)
+ def_other_element_without_accessor(name)
+ attr_reader(name)
+ converter = ""
+ if type == :integer
+ converter = "{|v| Integer(v)}"
+ end
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ def #{name}=(value)
+ @#{name} = Utils::CSV.parse(value)#{converter}
+ end
+ EOC
+ end
+ end
+
+ attr_reader :maker
+ def initialize(maker)
+ @maker = maker
+ @default_values_are_set = false
+ initialize_variables
+ end
+
+ def have_required_values?
+ not_set_required_variables.empty?
+ end
+
+ def variable_is_set?
+ variables.any? {|var| not __send__(var).nil?}
+ end
+
+ private
+ def initialize_variables
+ self.class.need_initialize_variables.each do |variable_name, init_value|
+ if init_value.nil?
+ value = nil
+ else
+ if init_value.respond_to?(:call)
+ value = init_value.call(self)
+ elsif init_value.is_a?(String)
+ # just for backward compatibility
+ value = instance_eval(init_value, __FILE__, __LINE__)
+ else
+ value = init_value
+ end
+ end
+ instance_variable_set("@#{variable_name}", value)
+ end
+ end
+
+ def setup_other_elements(feed, current=nil)
+ current ||= current_element(feed)
+ self.class.other_elements.each do |element|
+ __send__("setup_#{element}", feed, current)
+ end
+ end
+
+ def current_element(feed)
+ feed
+ end
+
+ def set_default_values(&block)
+ return yield if @default_values_are_set
+
+ begin
+ @default_values_are_set = true
+ _set_default_values(&block)
+ ensure
+ @default_values_are_set = false
+ end
+ end
+
+ def _set_default_values(&block)
+ yield
+ end
+
+ def setup_values(target)
+ set = false
+ if have_required_values?
+ variables.each do |var|
+ setter = "#{var}="
+ if target.respond_to?(setter)
+ value = __send__(var)
+ if value
+ target.__send__(setter, value)
+ set = true
+ end
+ end
+ end
+ end
+ set
+ end
+
+ def set_parent(target, parent)
+ target.parent = parent if target.class.need_parent?
+ end
+
+ def variables
+ self.class.need_initialize_variables.find_all do |name, init|
+ # init == "nil" is just for backward compatibility
+ init.nil? or init == "nil"
+ end.collect do |name, init|
+ name
+ end
+ end
+
+ def not_set_required_variables
+ required_variable_names.find_all do |var|
+ __send__(var).nil?
+ end
+ end
+
+ def required_variables_are_set?
+ required_variable_names.each do |var|
+ return false if __send__(var).nil?
+ end
+ true
+ end
+ end
+
+ module AtomPersonConstructBase
+ def self.append_features(klass)
+ super
+
+ klass.class_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ %w(name uri email).each do |element|
+ attr_accessor element
+ add_need_initialize_variable(element)
+ end
+ EOC
+ end
+ end
+
+ module AtomTextConstructBase
+ module EnsureXMLContent
+ class << self
+ def included(base)
+ super
+ base.class_eval do
+ %w(type content xml_content).each do |element|
+ attr_reader element
+ attr_writer element if element != "xml_content"
+ add_need_initialize_variable(element)
+ end
+
+ alias_method(:xhtml, :xml_content)
+ end
+ end
+ end
+
+ def ensure_xml_content(content)
+ xhtml_uri = ::RSS::Atom::XHTML_URI
+ unless content.is_a?(RSS::XML::Element) and
+ ["div", xhtml_uri] == [content.name, content.uri]
+ children = content
+ children = [children] unless content.is_a?(Array)
+ children = set_xhtml_uri_as_default_uri(children)
+ content = RSS::XML::Element.new("div", nil, xhtml_uri,
+ {"xmlns" => xhtml_uri},
+ children)
+ end
+ content
+ end
+
+ def xml_content=(content)
+ @xml_content = ensure_xml_content(content)
+ end
+
+ def xhtml=(content)
+ self.xml_content = content
+ end
+
+ private
+ def set_xhtml_uri_as_default_uri(children)
+ children.collect do |child|
+ if child.is_a?(RSS::XML::Element) and
+ child.prefix.nil? and child.uri.nil?
+ RSS::XML::Element.new(child.name, nil, ::RSS::Atom::XHTML_URI,
+ child.attributes.dup,
+ set_xhtml_uri_as_default_uri(child.children))
+ else
+ child
+ end
+ end
+ end
+ end
+
+ def self.append_features(klass)
+ super
+
+ klass.class_eval do
+ include EnsureXMLContent
+ end
+ end
+ end
+
+ module SetupDefaultDate
+ private
+ def _set_default_values(&block)
+ keep = {
+ :date => date,
+ :dc_dates => dc_dates.to_a.dup,
+ }
+ _date = date
+ if _date and !dc_dates.any? {|dc_date| dc_date.value == _date}
+ dc_date = self.class::DublinCoreDates::DublinCoreDate.new(self)
+ dc_date.value = _date.dup
+ dc_dates.unshift(dc_date)
+ end
+ self.date ||= self.dc_date
+ super(&block)
+ ensure
+ date = keep[:date]
+ dc_dates.replace(keep[:dc_dates])
+ end
+ end
+
+ class RSSBase < Base
+ class << self
+ def make(version, &block)
+ new(version).make(&block)
+ end
+ end
+
+ %w(xml_stylesheets channel image items textinput).each do |element|
+ attr_reader element
+ add_need_initialize_variable(element) do |object|
+ object.send("make_#{element}")
+ end
+ module_eval(<<-EOC, __FILE__, __LINE__)
+ private
+ def setup_#{element}(feed)
+ @#{element}.to_feed(feed)
+ end
+
+ def make_#{element}
+ self.class::#{Utils.to_class_name(element)}.new(self)
+ end
+ EOC
+ end
+
+ attr_reader :feed_version
+ alias_method(:rss_version, :feed_version)
+ attr_accessor :version, :encoding, :standalone
+
+ def initialize(feed_version)
+ super(self)
+ @feed_type = nil
+ @feed_subtype = nil
+ @feed_version = feed_version
+ @version = "1.0"
+ @encoding = "UTF-8"
+ @standalone = nil
+ end
+
+ def make
+ yield(self)
+ to_feed
+ end
+
+ def to_feed
+ feed = make_feed
+ setup_xml_stylesheets(feed)
+ setup_elements(feed)
+ setup_other_elements(feed)
+ feed.validate
+ feed
+ end
+
+ private
+ remove_method :make_xml_stylesheets
+ def make_xml_stylesheets
+ XMLStyleSheets.new(self)
+ end
+ end
+
+ class XMLStyleSheets < Base
+ def_array_element("xml_stylesheet", nil, "XMLStyleSheet")
+
+ class XMLStyleSheet < Base
+
+ ::RSS::XMLStyleSheet::ATTRIBUTES.each do |attribute|
+ attr_accessor attribute
+ add_need_initialize_variable(attribute)
+ end
+
+ def to_feed(feed)
+ xss = ::RSS::XMLStyleSheet.new
+ guess_type_if_need(xss)
+ set = setup_values(xss)
+ if set
+ feed.xml_stylesheets << xss
+ end
+ end
+
+ private
+ def guess_type_if_need(xss)
+ if @type.nil?
+ xss.href = @href
+ @type = xss.type
+ end
+ end
+
+ def required_variable_names
+ %w(href type)
+ end
+ end
+ end
+
+ class ChannelBase < Base
+ include SetupDefaultDate
+
+ %w(cloud categories skipDays skipHours).each do |name|
+ def_classed_element(name)
+ end
+
+ %w(generator copyright description title).each do |name|
+ def_classed_element(name, nil, "content")
+ end
+
+ [
+ ["link", "href", Proc.new {|target,| "#{target}.href = 'self'"}],
+ ["author", "name"],
+ ["contributor", "name"],
+ ].each do |name, attribute, additional_setup_maker|
+ def_classed_elements(name, attribute, &additional_setup_maker)
+ end
+
+ %w(id about language
+ managingEditor webMaster rating docs date
+ lastBuildDate ttl).each do |element|
+ attr_accessor element
+ add_need_initialize_variable(element)
+ end
+
+ def pubDate
+ date
+ end
+
+ def pubDate=(date)
+ self.date = date
+ end
+
+ def updated
+ date
+ end
+
+ def updated=(date)
+ self.date = date
+ end
+
+ alias_method(:rights, :copyright)
+ alias_method(:rights=, :copyright=)
+
+ alias_method(:subtitle, :description)
+ alias_method(:subtitle=, :description=)
+
+ def icon
+ image_favicon.about
+ end
+
+ def icon=(url)
+ image_favicon.about = url
+ end
+
+ def logo
+ maker.image.url
+ end
+
+ def logo=(url)
+ maker.image.url = url
+ end
+
+ class SkipDaysBase < Base
+ def_array_element("day")
+
+ class DayBase < Base
+ %w(content).each do |element|
+ attr_accessor element
+ add_need_initialize_variable(element)
+ end
+ end
+ end
+
+ class SkipHoursBase < Base
+ def_array_element("hour")
+
+ class HourBase < Base
+ %w(content).each do |element|
+ attr_accessor element
+ add_need_initialize_variable(element)
+ end
+ end
+ end
+
+ class CloudBase < Base
+ %w(domain port path registerProcedure protocol).each do |element|
+ attr_accessor element
+ add_need_initialize_variable(element)
+ end
+ end
+
+ class CategoriesBase < Base
+ def_array_element("category", "categories")
+
+ class CategoryBase < Base
+ %w(domain content label).each do |element|
+ attr_accessor element
+ add_need_initialize_variable(element)
+ end
+
+ alias_method(:term, :domain)
+ alias_method(:term=, :domain=)
+ alias_method(:scheme, :content)
+ alias_method(:scheme=, :content=)
+ end
+ end
+
+ class LinksBase < Base
+ def_array_element("link")
+
+ class LinkBase < Base
+ %w(href rel type hreflang title length).each do |element|
+ attr_accessor element
+ add_need_initialize_variable(element)
+ end
+ end
+ end
+
+ class AuthorsBase < Base
+ def_array_element("author")
+
+ class AuthorBase < Base
+ include AtomPersonConstructBase
+ end
+ end
+
+ class ContributorsBase < Base
+ def_array_element("contributor")
+
+ class ContributorBase < Base
+ include AtomPersonConstructBase
+ end
+ end
+
+ class GeneratorBase < Base
+ %w(uri version content).each do |element|
+ attr_accessor element
+ add_need_initialize_variable(element)
+ end
+ end
+
+ class CopyrightBase < Base
+ include AtomTextConstructBase
+ end
+
+ class DescriptionBase < Base
+ include AtomTextConstructBase
+ end
+
+ class TitleBase < Base
+ include AtomTextConstructBase
+ end
+ end
+
+ class ImageBase < Base
+ %w(title url width height description).each do |element|
+ attr_accessor element
+ add_need_initialize_variable(element)
+ end
+
+ def link
+ @maker.channel.link
+ end
+ end
+
+ class ItemsBase < Base
+ def_array_element("item")
+
+ attr_accessor :do_sort, :max_size
+
+ def initialize(maker)
+ super
+ @do_sort = false
+ @max_size = -1
+ end
+
+ def normalize
+ if @max_size >= 0
+ sort_if_need[0...@max_size]
+ else
+ sort_if_need[0..@max_size]
+ end
+ end
+
+ private
+ def sort_if_need
+ if @do_sort.respond_to?(:call)
+ @items.sort do |x, y|
+ @do_sort.call(x, y)
+ end
+ elsif @do_sort
+ @items.sort do |x, y|
+ y <=> x
+ end
+ else
+ @items
+ end
+ end
+
+ class ItemBase < Base
+ include SetupDefaultDate
+
+ %w(guid enclosure source categories content).each do |name|
+ def_classed_element(name)
+ end
+
+ %w(rights description title).each do |name|
+ def_classed_element(name, nil, "content")
+ end
+
+ [
+ ["author", "name"],
+ ["link", "href", Proc.new {|target,| "#{target}.href = 'alternate'"}],
+ ["contributor", "name"],
+ ].each do |name, attribute|
+ def_classed_elements(name, attribute)
+ end
+
+ %w(date comments id published).each do |element|
+ attr_accessor element
+ add_need_initialize_variable(element)
+ end
+
+ def pubDate
+ date
+ end
+
+ def pubDate=(date)
+ self.date = date
+ end
+
+ def updated
+ date
+ end
+
+ def updated=(date)
+ self.date = date
+ end
+
+ alias_method(:summary, :description)
+ alias_method(:summary=, :description=)
+
+ def <=>(other)
+ _date = date || dc_date
+ _other_date = other.date || other.dc_date
+ if _date and _other_date
+ _date <=> _other_date
+ elsif _date
+ 1
+ elsif _other_date
+ -1
+ else
+ 0
+ end
+ end
+
+ class GuidBase < Base
+ %w(isPermaLink content).each do |element|
+ attr_accessor element
+ add_need_initialize_variable(element)
+ end
+ end
+
+ class EnclosureBase < Base
+ %w(url length type).each do |element|
+ attr_accessor element
+ add_need_initialize_variable(element)
+ end
+ end
+
+ class SourceBase < Base
+ %w(authors categories contributors generator icon
+ logo rights subtitle title).each do |name|
+ def_classed_element(name)
+ end
+
+ [
+ ["link", "href"],
+ ].each do |name, attribute|
+ def_classed_elements(name, attribute)
+ end
+
+ %w(id content date).each do |element|
+ attr_accessor element
+ add_need_initialize_variable(element)
+ end
+
+ alias_method(:url, :link)
+ alias_method(:url=, :link=)
+
+ def updated
+ date
+ end
+
+ def updated=(date)
+ self.date = date
+ end
+
+ private
+ AuthorsBase = ChannelBase::AuthorsBase
+ CategoriesBase = ChannelBase::CategoriesBase
+ ContributorsBase = ChannelBase::ContributorsBase
+ GeneratorBase = ChannelBase::GeneratorBase
+
+ class IconBase < Base
+ %w(url).each do |element|
+ attr_accessor element
+ add_need_initialize_variable(element)
+ end
+ end
+
+ LinksBase = ChannelBase::LinksBase
+
+ class LogoBase < Base
+ %w(uri).each do |element|
+ attr_accessor element
+ add_need_initialize_variable(element)
+ end
+ end
+
+ class RightsBase < Base
+ include AtomTextConstructBase
+ end
+
+ class SubtitleBase < Base
+ include AtomTextConstructBase
+ end
+
+ class TitleBase < Base
+ include AtomTextConstructBase
+ end
+ end
+
+ CategoriesBase = ChannelBase::CategoriesBase
+ AuthorsBase = ChannelBase::AuthorsBase
+ LinksBase = ChannelBase::LinksBase
+ ContributorsBase = ChannelBase::ContributorsBase
+
+ class RightsBase < Base
+ include AtomTextConstructBase
+ end
+
+ class DescriptionBase < Base
+ include AtomTextConstructBase
+ end
+
+ class ContentBase < Base
+ include AtomTextConstructBase::EnsureXMLContent
+
+ %w(src).each do |element|
+ attr_accessor(element)
+ add_need_initialize_variable(element)
+ end
+
+ def xml_content=(content)
+ content = ensure_xml_content(content) if inline_xhtml?
+ @xml_content = content
+ end
+
+ alias_method(:xml, :xml_content)
+ alias_method(:xml=, :xml_content=)
+
+ def inline_text?
+ [nil, "text", "html"].include?(@type)
+ end
+
+ def inline_html?
+ @type == "html"
+ end
+
+ def inline_xhtml?
+ @type == "xhtml"
+ end
+
+ def inline_other?
+ !out_of_line? and ![nil, "text", "html", "xhtml"].include?(@type)
+ end
+
+ def inline_other_text?
+ return false if @type.nil? or out_of_line?
+ /\Atext\//i.match(@type) ? true : false
+ end
+
+ def inline_other_xml?
+ return false if @type.nil? or out_of_line?
+ /[\+\/]xml\z/i.match(@type) ? true : false
+ end
+
+ def inline_other_base64?
+ return false if @type.nil? or out_of_line?
+ @type.include?("/") and !inline_other_text? and !inline_other_xml?
+ end
+
+ def out_of_line?
+ not @src.nil? and @content.nil?
+ end
+ end
+
+ class TitleBase < Base
+ include AtomTextConstructBase
+ end
+ end
+ end
+
+ class TextinputBase < Base
+ %w(title description name link).each do |element|
+ attr_accessor element
+ add_need_initialize_variable(element)
+ end
+ end
+ end
+end