summaryrefslogtreecommitdiff
path: root/trunk/lib/rss
diff options
context:
space:
mode:
authoryugui <yugui@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2008-08-25 15:02:05 +0000
committeryugui <yugui@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2008-08-25 15:02:05 +0000
commit0dc342de848a642ecce8db697b8fecd83a63e117 (patch)
tree2b7ed4724aff1f86073e4740134bda9c4aac1a39 /trunk/lib/rss
parentef70cf7138ab8034b5b806f466e4b484b24f0f88 (diff)
added tag v1_9_0_4
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/tags/v1_9_0_4@18845 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'trunk/lib/rss')
-rw-r--r--trunk/lib/rss/0.9.rb428
-rw-r--r--trunk/lib/rss/1.0.rb452
-rw-r--r--trunk/lib/rss/2.0.rb111
-rw-r--r--trunk/lib/rss/atom.rb748
-rw-r--r--trunk/lib/rss/content.rb31
-rw-r--r--trunk/lib/rss/content/1.0.rb10
-rw-r--r--trunk/lib/rss/content/2.0.rb12
-rw-r--r--trunk/lib/rss/converter.rb162
-rw-r--r--trunk/lib/rss/dublincore.rb161
-rw-r--r--trunk/lib/rss/dublincore/1.0.rb13
-rw-r--r--trunk/lib/rss/dublincore/2.0.rb13
-rw-r--r--trunk/lib/rss/dublincore/atom.rb17
-rw-r--r--trunk/lib/rss/image.rb193
-rw-r--r--trunk/lib/rss/itunes.rb410
-rw-r--r--trunk/lib/rss/maker.rb44
-rw-r--r--trunk/lib/rss/maker/0.9.rb467
-rw-r--r--trunk/lib/rss/maker/1.0.rb434
-rw-r--r--trunk/lib/rss/maker/2.0.rb223
-rw-r--r--trunk/lib/rss/maker/atom.rb172
-rw-r--r--trunk/lib/rss/maker/base.rb880
-rw-r--r--trunk/lib/rss/maker/content.rb21
-rw-r--r--trunk/lib/rss/maker/dublincore.rb124
-rw-r--r--trunk/lib/rss/maker/entry.rb163
-rw-r--r--trunk/lib/rss/maker/feed.rb429
-rw-r--r--trunk/lib/rss/maker/image.rb111
-rw-r--r--trunk/lib/rss/maker/itunes.rb242
-rw-r--r--trunk/lib/rss/maker/slash.rb33
-rw-r--r--trunk/lib/rss/maker/syndication.rb18
-rw-r--r--trunk/lib/rss/maker/taxonomy.rb118
-rw-r--r--trunk/lib/rss/maker/trackback.rb61
-rw-r--r--trunk/lib/rss/parser.rb551
-rw-r--r--trunk/lib/rss/rexmlparser.rb54
-rw-r--r--trunk/lib/rss/rss.rb1313
-rw-r--r--trunk/lib/rss/slash.rb49
-rw-r--r--trunk/lib/rss/syndication.rb67
-rw-r--r--trunk/lib/rss/taxonomy.rb145
-rw-r--r--trunk/lib/rss/trackback.rb288
-rw-r--r--trunk/lib/rss/utils.rb111
-rw-r--r--trunk/lib/rss/xml-stylesheet.rb105
-rw-r--r--trunk/lib/rss/xml.rb71
-rw-r--r--trunk/lib/rss/xmlparser.rb93
-rw-r--r--trunk/lib/rss/xmlscanner.rb121
42 files changed, 9269 insertions, 0 deletions
diff --git a/trunk/lib/rss/0.9.rb b/trunk/lib/rss/0.9.rb
new file mode 100644
index 0000000000..7b24e7596d
--- /dev/null
+++ b/trunk/lib/rss/0.9.rb
@@ -0,0 +1,428 @@
+require "rss/parser"
+
+module RSS
+
+ module RSS09
+ NSPOOL = {}
+ ELEMENTS = []
+
+ def self.append_features(klass)
+ super
+
+ klass.install_must_call_validator('', "")
+ end
+ end
+
+ class Rss < Element
+
+ include RSS09
+ include RootElementMixin
+
+ %w(channel).each do |name|
+ install_have_child_element(name, "", nil)
+ end
+
+ attr_writer :feed_version
+ alias_method(:rss_version, :feed_version)
+ alias_method(:rss_version=, :feed_version=)
+
+ def initialize(feed_version, version=nil, encoding=nil, standalone=nil)
+ super
+ @feed_type = "rss"
+ end
+
+ def items
+ if @channel
+ @channel.items
+ else
+ []
+ end
+ end
+
+ def image
+ if @channel
+ @channel.image
+ else
+ nil
+ end
+ end
+
+ def textinput
+ if @channel
+ @channel.textInput
+ else
+ nil
+ end
+ end
+
+ def setup_maker_elements(maker)
+ super
+ items.each do |item|
+ item.setup_maker(maker.items)
+ end
+ image.setup_maker(maker) if image
+ textinput.setup_maker(maker) if textinput
+ end
+
+ private
+ def _attrs
+ [
+ ["version", true, "feed_version"],
+ ]
+ end
+
+ class Channel < Element
+
+ include RSS09
+
+ [
+ ["title", nil, :text],
+ ["link", nil, :text],
+ ["description", nil, :text],
+ ["language", nil, :text],
+ ["copyright", "?", :text],
+ ["managingEditor", "?", :text],
+ ["webMaster", "?", :text],
+ ["rating", "?", :text],
+ ["pubDate", "?", :date, :rfc822],
+ ["lastBuildDate", "?", :date, :rfc822],
+ ["docs", "?", :text],
+ ["cloud", "?", :have_attribute],
+ ["skipDays", "?", :have_child],
+ ["skipHours", "?", :have_child],
+ ["image", nil, :have_child],
+ ["item", "*", :have_children],
+ ["textInput", "?", :have_child],
+ ].each do |name, occurs, type, *args|
+ __send__("install_#{type}_element", name, "", occurs, name, *args)
+ end
+ alias date pubDate
+ alias date= pubDate=
+
+ private
+ def maker_target(maker)
+ maker.channel
+ end
+
+ def setup_maker_elements(channel)
+ super
+ [
+ [skipDays, "day"],
+ [skipHours, "hour"],
+ ].each do |skip, key|
+ if skip
+ skip.__send__("#{key}s").each do |val|
+ target_skips = channel.__send__("skip#{key.capitalize}s")
+ new_target = target_skips.__send__("new_#{key}")
+ new_target.content = val.content
+ end
+ end
+ end
+ end
+
+ def not_need_to_call_setup_maker_variables
+ %w(image textInput)
+ end
+
+ class SkipDays < Element
+ include RSS09
+
+ [
+ ["day", "*"]
+ ].each do |name, occurs|
+ install_have_children_element(name, "", occurs)
+ end
+
+ class Day < Element
+ include RSS09
+
+ content_setup
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.content = args[0]
+ end
+ end
+
+ end
+
+ end
+
+ class SkipHours < Element
+ include RSS09
+
+ [
+ ["hour", "*"]
+ ].each do |name, occurs|
+ install_have_children_element(name, "", occurs)
+ end
+
+ class Hour < Element
+ include RSS09
+
+ content_setup(:integer)
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.content = args[0]
+ end
+ end
+ end
+
+ end
+
+ class Image < Element
+
+ include RSS09
+
+ %w(url title link).each do |name|
+ install_text_element(name, "", nil)
+ end
+ [
+ ["width", :integer],
+ ["height", :integer],
+ ["description"],
+ ].each do |name, type|
+ install_text_element(name, "", "?", name, type)
+ end
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.url = args[0]
+ self.title = args[1]
+ self.link = args[2]
+ self.width = args[3]
+ self.height = args[4]
+ self.description = args[5]
+ end
+ end
+
+ private
+ def maker_target(maker)
+ maker.image
+ end
+ end
+
+ class Cloud < Element
+
+ include RSS09
+
+ [
+ ["domain", "", true],
+ ["port", "", true, :integer],
+ ["path", "", true],
+ ["registerProcedure", "", true],
+ ["protocol", "", true],
+ ].each do |name, uri, required, type|
+ install_get_attribute(name, uri, required, type)
+ end
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.domain = args[0]
+ self.port = args[1]
+ self.path = args[2]
+ self.registerProcedure = args[3]
+ self.protocol = args[4]
+ end
+ end
+ end
+
+ class Item < Element
+
+ include RSS09
+
+ [
+ ["title", '?', :text],
+ ["link", '?', :text],
+ ["description", '?', :text],
+ ["category", '*', :have_children, "categories"],
+ ["source", '?', :have_child],
+ ["enclosure", '?', :have_child],
+ ].each do |tag, occurs, type, *args|
+ __send__("install_#{type}_element", tag, "", occurs, tag, *args)
+ end
+
+ private
+ def maker_target(items)
+ if items.respond_to?("items")
+ # For backward compatibility
+ items = items.items
+ end
+ items.new_item
+ end
+
+ def setup_maker_element(item)
+ super
+ @enclosure.setup_maker(item) if @enclosure
+ @source.setup_maker(item) if @source
+ end
+
+ class Source < Element
+
+ include RSS09
+
+ [
+ ["url", "", true]
+ ].each do |name, uri, required|
+ install_get_attribute(name, uri, required)
+ end
+
+ content_setup
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.url = args[0]
+ self.content = args[1]
+ end
+ end
+
+ private
+ def maker_target(item)
+ item.source
+ end
+
+ def setup_maker_attributes(source)
+ source.url = url
+ source.content = content
+ end
+ end
+
+ class Enclosure < Element
+
+ include RSS09
+
+ [
+ ["url", "", true],
+ ["length", "", true, :integer],
+ ["type", "", true],
+ ].each do |name, uri, required, type|
+ install_get_attribute(name, uri, required, type)
+ end
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.url = args[0]
+ self.length = args[1]
+ self.type = args[2]
+ end
+ end
+
+ private
+ def maker_target(item)
+ item.enclosure
+ end
+
+ def setup_maker_attributes(enclosure)
+ enclosure.url = url
+ enclosure.length = length
+ enclosure.type = type
+ end
+ end
+
+ class Category < Element
+
+ include RSS09
+
+ [
+ ["domain", "", false]
+ ].each do |name, uri, required|
+ install_get_attribute(name, uri, required)
+ end
+
+ content_setup
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.domain = args[0]
+ self.content = args[1]
+ end
+ end
+
+ private
+ def maker_target(item)
+ item.new_category
+ end
+
+ def setup_maker_attributes(category)
+ category.domain = domain
+ category.content = content
+ end
+
+ end
+
+ end
+
+ class TextInput < Element
+
+ include RSS09
+
+ %w(title description name link).each do |name|
+ install_text_element(name, "", nil)
+ end
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.title = args[0]
+ self.description = args[1]
+ self.name = args[2]
+ self.link = args[3]
+ end
+ end
+
+ private
+ def maker_target(maker)
+ maker.textinput
+ end
+ end
+
+ end
+
+ end
+
+ RSS09::ELEMENTS.each do |name|
+ BaseListener.install_get_text_element("", name, name)
+ end
+
+ module ListenerMixin
+ private
+ def initial_start_rss(tag_name, prefix, attrs, ns)
+ check_ns(tag_name, prefix, ns, "")
+
+ @rss = Rss.new(attrs['version'], @version, @encoding, @standalone)
+ @rss.do_validate = @do_validate
+ @rss.xml_stylesheets = @xml_stylesheets
+ @last_element = @rss
+ pr = Proc.new do |text, tags|
+ @rss.validate_for_stream(tags, @ignore_unknown_element) if @do_validate
+ end
+ @proc_stack.push(pr)
+ end
+
+ end
+
+end
diff --git a/trunk/lib/rss/1.0.rb b/trunk/lib/rss/1.0.rb
new file mode 100644
index 0000000000..f04e61c5eb
--- /dev/null
+++ b/trunk/lib/rss/1.0.rb
@@ -0,0 +1,452 @@
+require "rss/parser"
+
+module RSS
+
+ module RSS10
+ NSPOOL = {}
+ ELEMENTS = []
+
+ def self.append_features(klass)
+ super
+
+ klass.install_must_call_validator('', ::RSS::URI)
+ end
+
+ end
+
+ class RDF < Element
+
+ include RSS10
+ include RootElementMixin
+
+ class << self
+
+ def required_uri
+ URI
+ end
+
+ end
+
+ @tag_name = 'RDF'
+
+ PREFIX = 'rdf'
+ URI = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+
+ install_ns('', ::RSS::URI)
+ install_ns(PREFIX, URI)
+
+ [
+ ["channel", nil],
+ ["image", "?"],
+ ["item", "+", :children],
+ ["textinput", "?"],
+ ].each do |tag, occurs, type|
+ type ||= :child
+ __send__("install_have_#{type}_element", tag, ::RSS::URI, occurs)
+ end
+
+ alias_method(:rss_version, :feed_version)
+ def initialize(version=nil, encoding=nil, standalone=nil)
+ super('1.0', version, encoding, standalone)
+ @feed_type = "rss"
+ end
+
+ def full_name
+ tag_name_with_prefix(PREFIX)
+ end
+
+ class Li < Element
+
+ include RSS10
+
+ class << self
+ def required_uri
+ URI
+ end
+ end
+
+ [
+ ["resource", [URI, ""], true]
+ ].each do |name, uri, required|
+ install_get_attribute(name, uri, required)
+ end
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.resource = args[0]
+ end
+ end
+
+ def full_name
+ tag_name_with_prefix(PREFIX)
+ end
+ end
+
+ class Seq < Element
+
+ include RSS10
+
+ Li = ::RSS::RDF::Li
+
+ class << self
+ def required_uri
+ URI
+ end
+ end
+
+ @tag_name = 'Seq'
+
+ install_have_children_element("li", URI, "*")
+ install_must_call_validator('rdf', ::RSS::RDF::URI)
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ @li = args[0] if args[0]
+ end
+ end
+
+ def full_name
+ tag_name_with_prefix(PREFIX)
+ end
+
+ def setup_maker(target)
+ lis.each do |li|
+ target << li.resource
+ end
+ end
+ end
+
+ class Bag < Element
+
+ include RSS10
+
+ Li = ::RSS::RDF::Li
+
+ class << self
+ def required_uri
+ URI
+ end
+ end
+
+ @tag_name = 'Bag'
+
+ install_have_children_element("li", URI, "*")
+ install_must_call_validator('rdf', URI)
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ @li = args[0] if args[0]
+ end
+ end
+
+ def full_name
+ tag_name_with_prefix(PREFIX)
+ end
+
+ def setup_maker(target)
+ lis.each do |li|
+ target << li.resource
+ end
+ end
+ end
+
+ class Channel < Element
+
+ include RSS10
+
+ class << self
+
+ def required_uri
+ ::RSS::URI
+ end
+
+ end
+
+ [
+ ["about", URI, true]
+ ].each do |name, uri, required|
+ install_get_attribute(name, uri, required, nil, nil,
+ "#{PREFIX}:#{name}")
+ end
+
+ [
+ ['title', nil, :text],
+ ['link', nil, :text],
+ ['description', nil, :text],
+ ['image', '?', :have_child],
+ ['items', nil, :have_child],
+ ['textinput', '?', :have_child],
+ ].each do |tag, occurs, type|
+ __send__("install_#{type}_element", tag, ::RSS::URI, occurs)
+ end
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.about = args[0]
+ end
+ end
+
+ private
+ def maker_target(maker)
+ maker.channel
+ end
+
+ def setup_maker_attributes(channel)
+ channel.about = about
+ end
+
+ class Image < Element
+
+ include RSS10
+
+ class << self
+
+ def required_uri
+ ::RSS::URI
+ end
+
+ end
+
+ [
+ ["resource", URI, true]
+ ].each do |name, uri, required|
+ install_get_attribute(name, uri, required, nil, nil,
+ "#{PREFIX}:#{name}")
+ end
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.resource = args[0]
+ end
+ end
+ end
+
+ class Textinput < Element
+
+ include RSS10
+
+ class << self
+
+ def required_uri
+ ::RSS::URI
+ end
+
+ end
+
+ [
+ ["resource", URI, true]
+ ].each do |name, uri, required|
+ install_get_attribute(name, uri, required, nil, nil,
+ "#{PREFIX}:#{name}")
+ end
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.resource = args[0]
+ end
+ end
+ end
+
+ class Items < Element
+
+ include RSS10
+
+ Seq = ::RSS::RDF::Seq
+
+ class << self
+
+ def required_uri
+ ::RSS::URI
+ end
+
+ end
+
+ install_have_child_element("Seq", URI, nil)
+ install_must_call_validator('rdf', URI)
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.Seq = args[0]
+ end
+ self.Seq ||= Seq.new
+ end
+
+ def resources
+ if @Seq
+ @Seq.lis.collect do |li|
+ li.resource
+ end
+ else
+ []
+ end
+ end
+ end
+ end
+
+ class Image < Element
+
+ include RSS10
+
+ class << self
+
+ def required_uri
+ ::RSS::URI
+ end
+
+ end
+
+ [
+ ["about", URI, true]
+ ].each do |name, uri, required|
+ install_get_attribute(name, uri, required, nil, nil,
+ "#{PREFIX}:#{name}")
+ end
+
+ %w(title url link).each do |name|
+ install_text_element(name, ::RSS::URI, nil)
+ end
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.about = args[0]
+ end
+ end
+
+ private
+ def maker_target(maker)
+ maker.image
+ end
+ end
+
+ class Item < Element
+
+ include RSS10
+
+ class << self
+
+ def required_uri
+ ::RSS::URI
+ end
+
+ end
+
+
+ [
+ ["about", URI, true]
+ ].each do |name, uri, required|
+ install_get_attribute(name, uri, required, nil, nil,
+ "#{PREFIX}:#{name}")
+ end
+
+ [
+ ["title", nil],
+ ["link", nil],
+ ["description", "?"],
+ ].each do |tag, occurs|
+ install_text_element(tag, ::RSS::URI, occurs)
+ end
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.about = args[0]
+ end
+ end
+
+ private
+ def maker_target(items)
+ if items.respond_to?("items")
+ # For backward compatibility
+ items = items.items
+ end
+ items.new_item
+ end
+ end
+
+ class Textinput < Element
+
+ include RSS10
+
+ class << self
+
+ def required_uri
+ ::RSS::URI
+ end
+
+ end
+
+ [
+ ["about", URI, true]
+ ].each do |name, uri, required|
+ install_get_attribute(name, uri, required, nil, nil,
+ "#{PREFIX}:#{name}")
+ end
+
+ %w(title description name link).each do |name|
+ install_text_element(name, ::RSS::URI, nil)
+ end
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.about = args[0]
+ end
+ end
+
+ private
+ def maker_target(maker)
+ maker.textinput
+ end
+ end
+
+ end
+
+ RSS10::ELEMENTS.each do |name|
+ BaseListener.install_get_text_element(URI, name, name)
+ end
+
+ module ListenerMixin
+ private
+ def initial_start_RDF(tag_name, prefix, attrs, ns)
+ check_ns(tag_name, prefix, ns, RDF::URI)
+
+ @rss = RDF.new(@version, @encoding, @standalone)
+ @rss.do_validate = @do_validate
+ @rss.xml_stylesheets = @xml_stylesheets
+ @last_element = @rss
+ pr = Proc.new do |text, tags|
+ @rss.validate_for_stream(tags, @ignore_unknown_element) if @do_validate
+ end
+ @proc_stack.push(pr)
+ end
+ end
+
+end
diff --git a/trunk/lib/rss/2.0.rb b/trunk/lib/rss/2.0.rb
new file mode 100644
index 0000000000..3798da4eb7
--- /dev/null
+++ b/trunk/lib/rss/2.0.rb
@@ -0,0 +1,111 @@
+require "rss/0.9"
+
+module RSS
+
+ class Rss
+
+ class Channel
+
+ [
+ ["generator"],
+ ["ttl", :integer],
+ ].each do |name, type|
+ install_text_element(name, "", "?", name, type)
+ end
+
+ [
+ %w(category categories),
+ ].each do |name, plural_name|
+ install_have_children_element(name, "", "*", name, plural_name)
+ end
+
+ [
+ ["image", "?"],
+ ["language", "?"],
+ ].each do |name, occurs|
+ install_model(name, "", occurs)
+ end
+
+ Category = Item::Category
+
+ class Item
+
+ [
+ ["comments", "?"],
+ ["author", "?"],
+ ].each do |name, occurs|
+ install_text_element(name, "", occurs)
+ end
+
+ [
+ ["pubDate", '?'],
+ ].each do |name, occurs|
+ install_date_element(name, "", occurs, name, 'rfc822')
+ end
+ alias date pubDate
+ alias date= pubDate=
+
+ [
+ ["guid", '?'],
+ ].each do |name, occurs|
+ install_have_child_element(name, "", occurs)
+ end
+
+ private
+ alias _setup_maker_element setup_maker_element
+ def setup_maker_element(item)
+ _setup_maker_element(item)
+ @guid.setup_maker(item) if @guid
+ end
+
+ class Guid < Element
+
+ include RSS09
+
+ [
+ ["isPermaLink", "", false, :boolean]
+ ].each do |name, uri, required, type|
+ install_get_attribute(name, uri, required, type)
+ end
+
+ content_setup
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.isPermaLink = args[0]
+ self.content = args[1]
+ end
+ end
+
+ alias_method :_PermaLink?, :PermaLink?
+ private :_PermaLink?
+ def PermaLink?
+ perma = _PermaLink?
+ perma or perma.nil?
+ end
+
+ private
+ def maker_target(item)
+ item.guid
+ end
+
+ def setup_maker_attributes(guid)
+ guid.isPermaLink = isPermaLink
+ guid.content = content
+ end
+ end
+
+ end
+
+ end
+
+ end
+
+ RSS09::ELEMENTS.each do |name|
+ BaseListener.install_get_text_element("", name, name)
+ end
+
+end
diff --git a/trunk/lib/rss/atom.rb b/trunk/lib/rss/atom.rb
new file mode 100644
index 0000000000..10282a8743
--- /dev/null
+++ b/trunk/lib/rss/atom.rb
@@ -0,0 +1,748 @@
+require 'rss/parser'
+
+module RSS
+ module Atom
+ URI = "http://www.w3.org/2005/Atom"
+ XHTML_URI = "http://www.w3.org/1999/xhtml"
+
+ module CommonModel
+ NSPOOL = {}
+ ELEMENTS = []
+
+ def self.append_features(klass)
+ super
+ klass.install_must_call_validator("atom", URI)
+ [
+ ["lang", :xml],
+ ["base", :xml],
+ ].each do |name, uri, required|
+ klass.install_get_attribute(name, uri, required, [nil, :inherit])
+ end
+ klass.class_eval do
+ class << self
+ def required_uri
+ URI
+ end
+
+ def need_parent?
+ true
+ end
+ end
+ end
+ end
+ end
+
+ module ContentModel
+ module ClassMethods
+ def content_type
+ @content_type ||= nil
+ end
+ end
+
+ class << self
+ def append_features(klass)
+ super
+ klass.extend(ClassMethods)
+ klass.content_setup(klass.content_type, klass.tag_name)
+ end
+ end
+
+ def maker_target(target)
+ target
+ end
+
+ private
+ def setup_maker_element_writer
+ "#{self.class.name.split(/::/).last.downcase}="
+ end
+
+ def setup_maker_element(target)
+ target.__send__(setup_maker_element_writer, content)
+ super
+ end
+ end
+
+ module URIContentModel
+ class << self
+ def append_features(klass)
+ super
+ klass.class_eval do
+ @content_type = [nil, :uri]
+ include(ContentModel)
+ end
+ end
+ end
+ end
+
+ module TextConstruct
+ def self.append_features(klass)
+ super
+ klass.class_eval do
+ [
+ ["type", ""],
+ ].each do |name, uri, required|
+ install_get_attribute(name, uri, required, :text_type)
+ end
+
+ content_setup
+ add_need_initialize_variable("xhtml")
+
+ class << self
+ def xml_getter
+ "xhtml"
+ end
+
+ def xml_setter
+ "xhtml="
+ end
+ end
+ end
+ end
+
+ attr_writer :xhtml
+ def xhtml
+ return @xhtml if @xhtml.nil?
+ if @xhtml.is_a?(XML::Element) and
+ [@xhtml.name, @xhtml.uri] == ["div", XHTML_URI]
+ return @xhtml
+ end
+
+ children = @xhtml
+ children = [children] unless children.is_a?(Array)
+ XML::Element.new("div", nil, XHTML_URI,
+ {"xmlns" => XHTML_URI}, children)
+ end
+
+ def have_xml_content?
+ @type == "xhtml"
+ end
+
+ def atom_validate(ignore_unknown_element, tags, uri)
+ if have_xml_content?
+ if @xhtml.nil?
+ raise MissingTagError.new("div", tag_name)
+ end
+ unless [@xhtml.name, @xhtml.uri] == ["div", XHTML_URI]
+ raise NotExpectedTagError.new(@xhtml.name, @xhtml.uri, tag_name)
+ end
+ end
+ end
+
+ private
+ def maker_target(target)
+ target.__send__(self.class.name.split(/::/).last.downcase) {|x| x}
+ end
+
+ def setup_maker_attributes(target)
+ target.type = type
+ target.content = content
+ target.xml_content = @xhtml
+ end
+ end
+
+ module PersonConstruct
+ def self.append_features(klass)
+ super
+ klass.class_eval do
+ [
+ ["name", nil],
+ ["uri", "?"],
+ ["email", "?"],
+ ].each do |tag, occurs|
+ install_have_attribute_element(tag, URI, occurs, nil, :content)
+ end
+ end
+ end
+
+ def maker_target(target)
+ target.__send__("new_#{self.class.name.split(/::/).last.downcase}")
+ end
+
+ class Name < RSS::Element
+ include CommonModel
+ include ContentModel
+ end
+
+ class Uri < RSS::Element
+ include CommonModel
+ include URIContentModel
+ end
+
+ class Email < RSS::Element
+ include CommonModel
+ include ContentModel
+ end
+ end
+
+ module DateConstruct
+ def self.append_features(klass)
+ super
+ klass.class_eval do
+ @content_type = :w3cdtf
+ include(ContentModel)
+ end
+ end
+
+ def atom_validate(ignore_unknown_element, tags, uri)
+ raise NotAvailableValueError.new(tag_name, "") if content.nil?
+ end
+ end
+
+ module DuplicateLinkChecker
+ def validate_duplicate_links(links)
+ link_infos = {}
+ links.each do |link|
+ rel = link.rel || "alternate"
+ next unless rel == "alternate"
+ key = [link.hreflang, link.type]
+ if link_infos.has_key?(key)
+ raise TooMuchTagError.new("link", tag_name)
+ end
+ link_infos[key] = true
+ end
+ end
+ end
+
+ class Feed < RSS::Element
+ include RootElementMixin
+ include CommonModel
+ include DuplicateLinkChecker
+
+ install_ns('', URI)
+
+ [
+ ["author", "*", :children],
+ ["category", "*", :children, "categories"],
+ ["contributor", "*", :children],
+ ["generator", "?"],
+ ["icon", "?", nil, :content],
+ ["id", nil, nil, :content],
+ ["link", "*", :children],
+ ["logo", "?"],
+ ["rights", "?"],
+ ["subtitle", "?", nil, :content],
+ ["title", nil, nil, :content],
+ ["updated", nil, nil, :content],
+ ["entry", "*", :children, "entries"],
+ ].each do |tag, occurs, type, *args|
+ type ||= :child
+ __send__("install_have_#{type}_element",
+ tag, URI, occurs, tag, *args)
+ end
+
+ def initialize(version=nil, encoding=nil, standalone=nil)
+ super("1.0", version, encoding, standalone)
+ @feed_type = "atom"
+ @feed_subtype = "feed"
+ end
+
+ alias_method :items, :entries
+
+ def have_author?
+ authors.any? {|author| !author.to_s.empty?} or
+ entries.any? {|entry| entry.have_author?(false)}
+ end
+
+ private
+ def atom_validate(ignore_unknown_element, tags, uri)
+ unless have_author?
+ raise MissingTagError.new("author", tag_name)
+ end
+ validate_duplicate_links(links)
+ end
+
+ def have_required_elements?
+ super and have_author?
+ end
+
+ def maker_target(maker)
+ maker.channel
+ end
+
+ def setup_maker_element(channel)
+ prev_dc_dates = channel.dc_dates.to_a.dup
+ super
+ channel.about = id.content if id
+ channel.dc_dates.replace(prev_dc_dates)
+ end
+
+ def setup_maker_elements(channel)
+ super
+ items = channel.maker.items
+ entries.each do |entry|
+ entry.setup_maker(items)
+ end
+ end
+
+ class Author < RSS::Element
+ include CommonModel
+ include PersonConstruct
+ end
+
+ class Category < RSS::Element
+ include CommonModel
+
+ [
+ ["term", "", true],
+ ["scheme", "", false, [nil, :uri]],
+ ["label", ""],
+ ].each do |name, uri, required, type|
+ install_get_attribute(name, uri, required, type)
+ end
+
+ private
+ def maker_target(target)
+ target.new_category
+ end
+ end
+
+ class Contributor < RSS::Element
+ include CommonModel
+ include PersonConstruct
+ end
+
+ class Generator < RSS::Element
+ include CommonModel
+ include ContentModel
+
+ [
+ ["uri", "", false, [nil, :uri]],
+ ["version", ""],
+ ].each do |name, uri, required, type|
+ install_get_attribute(name, uri, required, type)
+ end
+
+ private
+ def setup_maker_attributes(target)
+ target.generator do |generator|
+ generator.uri = uri if uri
+ generator.version = version if version
+ end
+ end
+ end
+
+ class Icon < RSS::Element
+ include CommonModel
+ include URIContentModel
+ end
+
+ class Id < RSS::Element
+ include CommonModel
+ include URIContentModel
+ end
+
+ class Link < RSS::Element
+ include CommonModel
+
+ [
+ ["href", "", true, [nil, :uri]],
+ ["rel", ""],
+ ["type", ""],
+ ["hreflang", ""],
+ ["title", ""],
+ ["length", ""],
+ ].each do |name, uri, required, type|
+ install_get_attribute(name, uri, required, type)
+ end
+
+ private
+ def maker_target(target)
+ target.new_link
+ end
+ end
+
+ class Logo < RSS::Element
+ include CommonModel
+ include URIContentModel
+
+ def maker_target(target)
+ target.maker.image
+ end
+
+ private
+ def setup_maker_element_writer
+ "url="
+ end
+ end
+
+ class Rights < RSS::Element
+ include CommonModel
+ include TextConstruct
+ end
+
+ class Subtitle < RSS::Element
+ include CommonModel
+ include TextConstruct
+ end
+
+ class Title < RSS::Element
+ include CommonModel
+ include TextConstruct
+ end
+
+ class Updated < RSS::Element
+ include CommonModel
+ include DateConstruct
+ end
+
+ class Entry < RSS::Element
+ include CommonModel
+ include DuplicateLinkChecker
+
+ [
+ ["author", "*", :children],
+ ["category", "*", :children, "categories"],
+ ["content", "?", :child],
+ ["contributor", "*", :children],
+ ["id", nil, nil, :content],
+ ["link", "*", :children],
+ ["published", "?", :child, :content],
+ ["rights", "?", :child],
+ ["source", "?"],
+ ["summary", "?", :child],
+ ["title", nil],
+ ["updated", nil, :child, :content],
+ ].each do |tag, occurs, type, *args|
+ type ||= :attribute
+ __send__("install_have_#{type}_element",
+ tag, URI, occurs, tag, *args)
+ end
+
+ def have_author?(check_parent=true)
+ authors.any? {|author| !author.to_s.empty?} or
+ (check_parent and @parent and @parent.have_author?) or
+ (source and source.have_author?)
+ end
+
+ private
+ def atom_validate(ignore_unknown_element, tags, uri)
+ unless have_author?
+ raise MissingTagError.new("author", tag_name)
+ end
+ validate_duplicate_links(links)
+ end
+
+ def have_required_elements?
+ super and have_author?
+ end
+
+ def maker_target(items)
+ if items.respond_to?("items")
+ # For backward compatibility
+ items = items.items
+ end
+ items.new_item
+ end
+
+ Author = Feed::Author
+ Category = Feed::Category
+
+ class Content < RSS::Element
+ include CommonModel
+
+ class << self
+ def xml_setter
+ "xml="
+ end
+
+ def xml_getter
+ "xml"
+ end
+ end
+
+ [
+ ["type", ""],
+ ["src", "", false, [nil, :uri]],
+ ].each do |name, uri, required, type|
+ install_get_attribute(name, uri, required, type)
+ end
+
+ content_setup
+ add_need_initialize_variable("xml")
+
+ attr_writer :xml
+ def have_xml_content?
+ inline_xhtml? or inline_other_xml?
+ end
+
+ def xml
+ return @xml unless inline_xhtml?
+ return @xml if @xml.nil?
+ if @xml.is_a?(XML::Element) and
+ [@xml.name, @xml.uri] == ["div", XHTML_URI]
+ return @xml
+ end
+
+ children = @xml
+ children = [children] unless children.is_a?(Array)
+ XML::Element.new("div", nil, XHTML_URI,
+ {"xmlns" => XHTML_URI}, children)
+ end
+
+ def xhtml
+ if inline_xhtml?
+ xml
+ else
+ nil
+ end
+ end
+
+ def atom_validate(ignore_unknown_element, tags, uri)
+ if out_of_line?
+ raise MissingAttributeError.new(tag_name, "type") if @type.nil?
+ unless (content.nil? or content.empty?)
+ raise NotAvailableValueError.new(tag_name, content)
+ end
+ elsif inline_xhtml?
+ if @xml.nil?
+ raise MissingTagError.new("div", tag_name)
+ end
+ unless @xml.name == "div" and @xml.uri == XHTML_URI
+ raise NotExpectedTagError.new(@xml.name, @xml.uri, tag_name)
+ end
+ end
+ end
+
+ def inline_text?
+ !out_of_line? and [nil, "text", "html"].include?(@type)
+ end
+
+ def inline_html?
+ return false if out_of_line?
+ @type == "html" or mime_split == ["text", "html"]
+ end
+
+ def inline_xhtml?
+ !out_of_line? and @type == "xhtml"
+ end
+
+ def inline_other?
+ return false if out_of_line?
+ media_type, subtype = mime_split
+ return false if media_type.nil? or subtype.nil?
+ true
+ end
+
+ def inline_other_text?
+ return false unless inline_other?
+ return false if inline_other_xml?
+
+ media_type, subtype = mime_split
+ return true if "text" == media_type.downcase
+ false
+ end
+
+ def inline_other_xml?
+ return false unless inline_other?
+
+ media_type, subtype = mime_split
+ normalized_mime_type = "#{media_type}/#{subtype}".downcase
+ if /(?:\+xml|^xml)$/ =~ subtype or
+ %w(text/xml-external-parsed-entity
+ application/xml-external-parsed-entity
+ application/xml-dtd).find {|x| x == normalized_mime_type}
+ return true
+ end
+ false
+ end
+
+ def inline_other_base64?
+ inline_other? and !inline_other_text? and !inline_other_xml?
+ end
+
+ def out_of_line?
+ not @src.nil?
+ end
+
+ def mime_split
+ media_type = subtype = nil
+ if /\A\s*([a-z]+)\/([a-z\+]+)\s*(?:;.*)?\z/i =~ @type.to_s
+ media_type = $1.downcase
+ subtype = $2.downcase
+ end
+ [media_type, subtype]
+ end
+
+ def need_base64_encode?
+ inline_other_base64?
+ end
+
+ private
+ def empty_content?
+ out_of_line? or super
+ end
+ end
+
+ Contributor = Feed::Contributor
+ Id = Feed::Id
+ Link = Feed::Link
+
+ class Published < RSS::Element
+ include CommonModel
+ include DateConstruct
+ end
+
+ Rights = Feed::Rights
+
+ class Source < RSS::Element
+ include CommonModel
+
+ [
+ ["author", "*", :children],
+ ["category", "*", :children, "categories"],
+ ["contributor", "*", :children],
+ ["generator", "?"],
+ ["icon", "?"],
+ ["id", "?", nil, :content],
+ ["link", "*", :children],
+ ["logo", "?"],
+ ["rights", "?"],
+ ["subtitle", "?"],
+ ["title", "?"],
+ ["updated", "?", nil, :content],
+ ].each do |tag, occurs, type, *args|
+ type ||= :attribute
+ __send__("install_have_#{type}_element",
+ tag, URI, occurs, tag, *args)
+ end
+
+ def have_author?
+ !author.to_s.empty?
+ end
+
+ Author = Feed::Author
+ Category = Feed::Category
+ Contributor = Feed::Contributor
+ Generator = Feed::Generator
+ Icon = Feed::Icon
+ Id = Feed::Id
+ Link = Feed::Link
+ Logo = Feed::Logo
+ Rights = Feed::Rights
+ Subtitle = Feed::Subtitle
+ Title = Feed::Title
+ Updated = Feed::Updated
+ end
+
+ class Summary < RSS::Element
+ include CommonModel
+ include TextConstruct
+ end
+
+ Title = Feed::Title
+ Updated = Feed::Updated
+ end
+ end
+
+ class Entry < RSS::Element
+ include RootElementMixin
+ include CommonModel
+ include DuplicateLinkChecker
+
+ [
+ ["author", "*", :children],
+ ["category", "*", :children, "categories"],
+ ["content", "?"],
+ ["contributor", "*", :children],
+ ["id", nil, nil, :content],
+ ["link", "*", :children],
+ ["published", "?", :child, :content],
+ ["rights", "?"],
+ ["source", "?"],
+ ["summary", "?"],
+ ["title", nil],
+ ["updated", nil, nil, :content],
+ ].each do |tag, occurs, type, *args|
+ type ||= :attribute
+ __send__("install_have_#{type}_element",
+ tag, URI, occurs, tag, *args)
+ end
+
+ def initialize(version=nil, encoding=nil, standalone=nil)
+ super("1.0", version, encoding, standalone)
+ @feed_type = "atom"
+ @feed_subtype = "entry"
+ end
+
+ def items
+ [self]
+ end
+
+ def setup_maker(maker)
+ maker = maker.maker if maker.respond_to?("maker")
+ super(maker)
+ end
+
+ def have_author?
+ authors.any? {|author| !author.to_s.empty?} or
+ (source and source.have_author?)
+ end
+
+ private
+ def atom_validate(ignore_unknown_element, tags, uri)
+ unless have_author?
+ raise MissingTagError.new("author", tag_name)
+ end
+ validate_duplicate_links(links)
+ end
+
+ def have_required_elements?
+ super and have_author?
+ end
+
+ def maker_target(maker)
+ maker.items.new_item
+ end
+
+ Author = Feed::Entry::Author
+ Category = Feed::Entry::Category
+ Content = Feed::Entry::Content
+ Contributor = Feed::Entry::Contributor
+ Id = Feed::Entry::Id
+ Link = Feed::Entry::Link
+ Published = Feed::Entry::Published
+ Rights = Feed::Entry::Rights
+ Source = Feed::Entry::Source
+ Summary = Feed::Entry::Summary
+ Title = Feed::Entry::Title
+ Updated = Feed::Entry::Updated
+ end
+ end
+
+ Atom::CommonModel::ELEMENTS.each do |name|
+ BaseListener.install_get_text_element(Atom::URI, name, "#{name}=")
+ end
+
+ module ListenerMixin
+ private
+ def initial_start_feed(tag_name, prefix, attrs, ns)
+ check_ns(tag_name, prefix, ns, Atom::URI)
+
+ @rss = Atom::Feed.new(@version, @encoding, @standalone)
+ @rss.do_validate = @do_validate
+ @rss.xml_stylesheets = @xml_stylesheets
+ @rss.lang = attrs["xml:lang"]
+ @rss.base = attrs["xml:base"]
+ @last_element = @rss
+ pr = Proc.new do |text, tags|
+ @rss.validate_for_stream(tags) if @do_validate
+ end
+ @proc_stack.push(pr)
+ end
+
+ def initial_start_entry(tag_name, prefix, attrs, ns)
+ check_ns(tag_name, prefix, ns, Atom::URI)
+
+ @rss = Atom::Entry.new(@version, @encoding, @standalone)
+ @rss.do_validate = @do_validate
+ @rss.xml_stylesheets = @xml_stylesheets
+ @rss.lang = attrs["xml:lang"]
+ @rss.base = attrs["xml:base"]
+ @last_element = @rss
+ pr = Proc.new do |text, tags|
+ @rss.validate_for_stream(tags) if @do_validate
+ end
+ @proc_stack.push(pr)
+ end
+ end
+end
diff --git a/trunk/lib/rss/content.rb b/trunk/lib/rss/content.rb
new file mode 100644
index 0000000000..b12ee918aa
--- /dev/null
+++ b/trunk/lib/rss/content.rb
@@ -0,0 +1,31 @@
+require "rss/rss"
+
+module RSS
+ CONTENT_PREFIX = 'content'
+ CONTENT_URI = "http://purl.org/rss/1.0/modules/content/"
+
+ module ContentModel
+ extend BaseModel
+
+ ELEMENTS = ["#{CONTENT_PREFIX}_encoded"]
+
+ def self.append_features(klass)
+ super
+
+ klass.install_must_call_validator(CONTENT_PREFIX, CONTENT_URI)
+ ELEMENTS.each do |full_name|
+ name = full_name[(CONTENT_PREFIX.size + 1)..-1]
+ klass.install_text_element(name, CONTENT_URI, "?", full_name)
+ end
+ end
+ end
+
+ prefix_size = CONTENT_PREFIX.size + 1
+ ContentModel::ELEMENTS.each do |full_name|
+ name = full_name[prefix_size..-1]
+ BaseListener.install_get_text_element(CONTENT_URI, name, full_name)
+ end
+end
+
+require 'rss/content/1.0'
+require 'rss/content/2.0'
diff --git a/trunk/lib/rss/content/1.0.rb b/trunk/lib/rss/content/1.0.rb
new file mode 100644
index 0000000000..e7c0c19685
--- /dev/null
+++ b/trunk/lib/rss/content/1.0.rb
@@ -0,0 +1,10 @@
+require 'rss/1.0'
+require 'rss/content'
+
+module RSS
+ RDF.install_ns(CONTENT_PREFIX, CONTENT_URI)
+
+ class RDF
+ class Item; include ContentModel; end
+ end
+end
diff --git a/trunk/lib/rss/content/2.0.rb b/trunk/lib/rss/content/2.0.rb
new file mode 100644
index 0000000000..8671b5b1a6
--- /dev/null
+++ b/trunk/lib/rss/content/2.0.rb
@@ -0,0 +1,12 @@
+require "rss/2.0"
+require "rss/content"
+
+module RSS
+ Rss.install_ns(CONTENT_PREFIX, CONTENT_URI)
+
+ class Rss
+ class Channel
+ class Item; include ContentModel; end
+ end
+ end
+end
diff --git a/trunk/lib/rss/converter.rb b/trunk/lib/rss/converter.rb
new file mode 100644
index 0000000000..415a319188
--- /dev/null
+++ b/trunk/lib/rss/converter.rb
@@ -0,0 +1,162 @@
+require "rss/utils"
+
+module RSS
+
+ class Converter
+
+ include Utils
+
+ def initialize(to_enc, from_enc=nil)
+ normalized_to_enc = to_enc.downcase.gsub(/-/, '_')
+ from_enc ||= 'utf-8'
+ normalized_from_enc = from_enc.downcase.gsub(/-/, '_')
+ if normalized_to_enc == normalized_from_enc
+ def_same_enc()
+ else
+ def_diff_enc = "def_to_#{normalized_to_enc}_from_#{normalized_from_enc}"
+ if respond_to?(def_diff_enc)
+ __send__(def_diff_enc)
+ else
+ def_else_enc(to_enc, from_enc)
+ end
+ end
+ end
+
+ def convert(value)
+ value
+ end
+
+ def def_convert(depth=0)
+ instance_eval(<<-EOC, *get_file_and_line_from_caller(depth))
+ def convert(value)
+ if value.kind_of?(String)
+ #{yield('value')}
+ else
+ value
+ end
+ end
+ EOC
+ end
+
+ def def_iconv_convert(to_enc, from_enc, depth=0)
+ begin
+ require "iconv"
+ @iconv = Iconv.new(to_enc, from_enc)
+ def_convert(depth+1) do |value|
+ <<-EOC
+ begin
+ @iconv.iconv(#{value})
+ rescue Iconv::Failure
+ raise ConversionError.new(#{value}, "#{to_enc}", "#{from_enc}")
+ end
+ EOC
+ end
+ rescue LoadError, ArgumentError, SystemCallError
+ raise UnknownConversionMethodError.new(to_enc, from_enc)
+ end
+ end
+
+ def def_else_enc(to_enc, from_enc)
+ def_iconv_convert(to_enc, from_enc, 0)
+ end
+
+ def def_same_enc()
+ def_convert do |value|
+ value
+ end
+ end
+
+ def def_uconv_convert_if_can(meth, to_enc, from_enc, nkf_arg)
+ begin
+ require "uconv"
+ def_convert(1) do |value|
+ <<-EOC
+ begin
+ Uconv.#{meth}(#{value})
+ rescue Uconv::Error
+ raise ConversionError.new(#{value}, "#{to_enc}", "#{from_enc}")
+ end
+ EOC
+ end
+ rescue LoadError
+ require 'nkf'
+ if NKF.const_defined?(:UTF8)
+ def_convert(1) do |value|
+ "NKF.nkf(#{nkf_arg.dump}, #{value})"
+ end
+ else
+ def_iconv_convert(to_enc, from_enc, 1)
+ end
+ end
+ end
+
+ def def_to_euc_jp_from_utf_8
+ def_uconv_convert_if_can('u8toeuc', 'EUC-JP', 'UTF-8', '-We')
+ end
+
+ def def_to_utf_8_from_euc_jp
+ def_uconv_convert_if_can('euctou8', 'UTF-8', 'EUC-JP', '-Ew')
+ end
+
+ def def_to_shift_jis_from_utf_8
+ def_uconv_convert_if_can('u8tosjis', 'Shift_JIS', 'UTF-8', '-Ws')
+ end
+
+ def def_to_utf_8_from_shift_jis
+ def_uconv_convert_if_can('sjistou8', 'UTF-8', 'Shift_JIS', '-Sw')
+ end
+
+ def def_to_euc_jp_from_shift_jis
+ require "nkf"
+ def_convert do |value|
+ "NKF.nkf('-Se', #{value})"
+ end
+ end
+
+ def def_to_shift_jis_from_euc_jp
+ require "nkf"
+ def_convert do |value|
+ "NKF.nkf('-Es', #{value})"
+ end
+ end
+
+ def def_to_euc_jp_from_iso_2022_jp
+ require "nkf"
+ def_convert do |value|
+ "NKF.nkf('-Je', #{value})"
+ end
+ end
+
+ def def_to_iso_2022_jp_from_euc_jp
+ require "nkf"
+ def_convert do |value|
+ "NKF.nkf('-Ej', #{value})"
+ end
+ end
+
+ def def_to_utf_8_from_iso_8859_1
+ def_convert do |value|
+ "#{value}.unpack('C*').pack('U*')"
+ end
+ end
+
+ def def_to_iso_8859_1_from_utf_8
+ def_convert do |value|
+ <<-EOC
+ array_utf8 = #{value}.unpack('U*')
+ array_enc = []
+ array_utf8.each do |num|
+ if num <= 0xFF
+ array_enc << num
+ else
+ array_enc.concat "&\#\#{num};".unpack('C*')
+ end
+ end
+ array_enc.pack('C*')
+ EOC
+ end
+ end
+
+ end
+
+end
diff --git a/trunk/lib/rss/dublincore.rb b/trunk/lib/rss/dublincore.rb
new file mode 100644
index 0000000000..7ba239f8f1
--- /dev/null
+++ b/trunk/lib/rss/dublincore.rb
@@ -0,0 +1,161 @@
+require "rss/rss"
+
+module RSS
+ DC_PREFIX = 'dc'
+ DC_URI = "http://purl.org/dc/elements/1.1/"
+
+ module BaseDublinCoreModel
+ def append_features(klass)
+ super
+
+ return if klass.instance_of?(Module)
+ DublinCoreModel::ELEMENT_NAME_INFOS.each do |name, plural_name|
+ plural = plural_name || "#{name}s"
+ full_name = "#{DC_PREFIX}_#{name}"
+ full_plural_name = "#{DC_PREFIX}_#{plural}"
+ klass_name = "DublinCore#{Utils.to_class_name(name)}"
+ klass.install_must_call_validator(DC_PREFIX, DC_URI)
+ klass.install_have_children_element(name, DC_URI, "*",
+ full_name, full_plural_name)
+ klass.module_eval(<<-EOC, *get_file_and_line_from_caller(0))
+ remove_method :#{full_name}
+ remove_method :#{full_name}=
+ remove_method :set_#{full_name}
+
+ def #{full_name}
+ @#{full_name}.first and @#{full_name}.first.value
+ end
+
+ def #{full_name}=(new_value)
+ @#{full_name}[0] = Utils.new_with_value_if_need(#{klass_name}, new_value)
+ end
+ alias set_#{full_name} #{full_name}=
+ EOC
+ end
+ klass.module_eval(<<-EOC, *get_file_and_line_from_caller(0))
+ if method_defined?(:date)
+ alias date_without_#{DC_PREFIX}_date= date=
+
+ def date=(value)
+ self.date_without_#{DC_PREFIX}_date = value
+ self.#{DC_PREFIX}_date = value
+ end
+ else
+ alias date #{DC_PREFIX}_date
+ alias date= #{DC_PREFIX}_date=
+ end
+
+ # For backward compatibility
+ alias #{DC_PREFIX}_rightses #{DC_PREFIX}_rights_list
+ EOC
+ end
+ end
+
+ module DublinCoreModel
+
+ extend BaseModel
+ extend BaseDublinCoreModel
+
+ TEXT_ELEMENTS = {
+ "title" => nil,
+ "description" => nil,
+ "creator" => nil,
+ "subject" => nil,
+ "publisher" => nil,
+ "contributor" => nil,
+ "type" => nil,
+ "format" => nil,
+ "identifier" => nil,
+ "source" => nil,
+ "language" => nil,
+ "relation" => nil,
+ "coverage" => nil,
+ "rights" => "rights_list"
+ }
+
+ DATE_ELEMENTS = {
+ "date" => "w3cdtf",
+ }
+
+ ELEMENT_NAME_INFOS = DublinCoreModel::TEXT_ELEMENTS.to_a
+ DublinCoreModel::DATE_ELEMENTS.each do |name, |
+ ELEMENT_NAME_INFOS << [name, nil]
+ end
+
+ ELEMENTS = TEXT_ELEMENTS.keys + DATE_ELEMENTS.keys
+
+ ELEMENTS.each do |name, plural_name|
+ module_eval(<<-EOC, *get_file_and_line_from_caller(0))
+ class DublinCore#{Utils.to_class_name(name)} < Element
+ include RSS10
+
+ content_setup
+
+ class << self
+ def required_prefix
+ DC_PREFIX
+ end
+
+ def required_uri
+ DC_URI
+ end
+ end
+
+ @tag_name = #{name.dump}
+
+ alias_method(:value, :content)
+ alias_method(:value=, :content=)
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.content = args[0]
+ end
+ end
+
+ def full_name
+ tag_name_with_prefix(DC_PREFIX)
+ end
+
+ def maker_target(target)
+ target.new_#{name}
+ end
+
+ def setup_maker_attributes(#{name})
+ #{name}.content = content
+ end
+ end
+ EOC
+ end
+
+ DATE_ELEMENTS.each do |name, type|
+ tag_name = "#{DC_PREFIX}:#{name}"
+ module_eval(<<-EOC, *get_file_and_line_from_caller(0))
+ class DublinCore#{Utils.to_class_name(name)} < Element
+ remove_method(:content=)
+ remove_method(:value=)
+
+ date_writer("content", #{type.dump}, #{tag_name.dump})
+
+ alias_method(:value=, :content=)
+ end
+ EOC
+ end
+ end
+
+ # For backward compatibility
+ DublincoreModel = DublinCoreModel
+
+ DublinCoreModel::ELEMENTS.each do |name|
+ class_name = Utils.to_class_name(name)
+ BaseListener.install_class_name(DC_URI, name, "DublinCore#{class_name}")
+ end
+
+ DublinCoreModel::ELEMENTS.collect! {|name| "#{DC_PREFIX}_#{name}"}
+end
+
+require 'rss/dublincore/1.0'
+require 'rss/dublincore/2.0'
+require 'rss/dublincore/atom'
diff --git a/trunk/lib/rss/dublincore/1.0.rb b/trunk/lib/rss/dublincore/1.0.rb
new file mode 100644
index 0000000000..e193c6d2c2
--- /dev/null
+++ b/trunk/lib/rss/dublincore/1.0.rb
@@ -0,0 +1,13 @@
+require "rss/1.0"
+require "rss/dublincore"
+
+module RSS
+ RDF.install_ns(DC_PREFIX, DC_URI)
+
+ class RDF
+ class Channel; include DublinCoreModel; end
+ class Image; include DublinCoreModel; end
+ class Item; include DublinCoreModel; end
+ class Textinput; include DublinCoreModel; end
+ end
+end
diff --git a/trunk/lib/rss/dublincore/2.0.rb b/trunk/lib/rss/dublincore/2.0.rb
new file mode 100644
index 0000000000..82ed1888c5
--- /dev/null
+++ b/trunk/lib/rss/dublincore/2.0.rb
@@ -0,0 +1,13 @@
+require "rss/2.0"
+require "rss/dublincore"
+
+module RSS
+ Rss.install_ns(DC_PREFIX, DC_URI)
+
+ class Rss
+ class Channel
+ include DublinCoreModel
+ class Item; include DublinCoreModel; end
+ end
+ end
+end
diff --git a/trunk/lib/rss/dublincore/atom.rb b/trunk/lib/rss/dublincore/atom.rb
new file mode 100644
index 0000000000..e78df4821b
--- /dev/null
+++ b/trunk/lib/rss/dublincore/atom.rb
@@ -0,0 +1,17 @@
+require "rss/atom"
+require "rss/dublincore"
+
+module RSS
+ module Atom
+ Feed.install_ns(DC_PREFIX, DC_URI)
+
+ class Feed
+ include DublinCoreModel
+ class Entry; include DublinCoreModel; end
+ end
+
+ class Entry
+ include DublinCoreModel
+ end
+ end
+end
diff --git a/trunk/lib/rss/image.rb b/trunk/lib/rss/image.rb
new file mode 100644
index 0000000000..c4714aea12
--- /dev/null
+++ b/trunk/lib/rss/image.rb
@@ -0,0 +1,193 @@
+require 'rss/1.0'
+require 'rss/dublincore'
+
+module RSS
+
+ IMAGE_PREFIX = 'image'
+ IMAGE_URI = 'http://purl.org/rss/1.0/modules/image/'
+
+ RDF.install_ns(IMAGE_PREFIX, IMAGE_URI)
+
+ IMAGE_ELEMENTS = []
+
+ %w(item favicon).each do |name|
+ class_name = Utils.to_class_name(name)
+ BaseListener.install_class_name(IMAGE_URI, name, "Image#{class_name}")
+ IMAGE_ELEMENTS << "#{IMAGE_PREFIX}_#{name}"
+ end
+
+ module ImageModelUtils
+ def validate_one_tag_name(ignore_unknown_element, name, tags)
+ if !ignore_unknown_element
+ invalid = tags.find {|tag| tag != name}
+ raise UnknownTagError.new(invalid, IMAGE_URI) if invalid
+ end
+ raise TooMuchTagError.new(name, tag_name) if tags.size > 1
+ end
+ end
+
+ module ImageItemModel
+ include ImageModelUtils
+ extend BaseModel
+
+ def self.append_features(klass)
+ super
+
+ klass.install_have_child_element("item", IMAGE_URI, "?",
+ "#{IMAGE_PREFIX}_item")
+ klass.install_must_call_validator(IMAGE_PREFIX, IMAGE_URI)
+ end
+
+ class ImageItem < Element
+ include RSS10
+ include DublinCoreModel
+
+ @tag_name = "item"
+
+ class << self
+ def required_prefix
+ IMAGE_PREFIX
+ end
+
+ def required_uri
+ IMAGE_URI
+ end
+ end
+
+ install_must_call_validator(IMAGE_PREFIX, IMAGE_URI)
+
+ [
+ ["about", ::RSS::RDF::URI, true],
+ ["resource", ::RSS::RDF::URI, false],
+ ].each do |name, uri, required|
+ install_get_attribute(name, uri, required, nil, nil,
+ "#{::RSS::RDF::PREFIX}:#{name}")
+ end
+
+ %w(width height).each do |tag|
+ full_name = "#{IMAGE_PREFIX}_#{tag}"
+ disp_name = "#{IMAGE_PREFIX}:#{tag}"
+ install_text_element(tag, IMAGE_URI, "?",
+ full_name, :integer, disp_name)
+ BaseListener.install_get_text_element(IMAGE_URI, tag, full_name)
+ end
+
+ alias width= image_width=
+ alias width image_width
+ alias height= image_height=
+ alias height image_height
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.about = args[0]
+ self.resource = args[1]
+ end
+ end
+
+ def full_name
+ tag_name_with_prefix(IMAGE_PREFIX)
+ end
+
+ private
+ def maker_target(target)
+ target.image_item
+ end
+
+ def setup_maker_attributes(item)
+ item.about = self.about
+ item.resource = self.resource
+ end
+ end
+ end
+
+ module ImageFaviconModel
+ include ImageModelUtils
+ extend BaseModel
+
+ def self.append_features(klass)
+ super
+
+ unless klass.class == Module
+ klass.install_have_child_element("favicon", IMAGE_URI, "?",
+ "#{IMAGE_PREFIX}_favicon")
+ klass.install_must_call_validator(IMAGE_PREFIX, IMAGE_URI)
+ end
+ end
+
+ class ImageFavicon < Element
+ include RSS10
+ include DublinCoreModel
+
+ @tag_name = "favicon"
+
+ class << self
+ def required_prefix
+ IMAGE_PREFIX
+ end
+
+ def required_uri
+ IMAGE_URI
+ end
+ end
+
+ [
+ ["about", ::RSS::RDF::URI, true, ::RSS::RDF::PREFIX],
+ ["size", IMAGE_URI, true, IMAGE_PREFIX],
+ ].each do |name, uri, required, prefix|
+ install_get_attribute(name, uri, required, nil, nil,
+ "#{prefix}:#{name}")
+ end
+
+ AVAILABLE_SIZES = %w(small medium large)
+ alias_method :set_size, :size=
+ private :set_size
+ def size=(new_value)
+ if @do_validate and !new_value.nil?
+ new_value = new_value.strip
+ unless AVAILABLE_SIZES.include?(new_value)
+ attr_name = "#{IMAGE_PREFIX}:size"
+ raise NotAvailableValueError.new(full_name, new_value, attr_name)
+ end
+ end
+ set_size(new_value)
+ end
+
+ alias image_size= size=
+ alias image_size size
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.about = args[0]
+ self.size = args[1]
+ end
+ end
+
+ def full_name
+ tag_name_with_prefix(IMAGE_PREFIX)
+ end
+
+ private
+ def maker_target(target)
+ target.image_favicon
+ end
+
+ def setup_maker_attributes(favicon)
+ favicon.about = self.about
+ favicon.size = self.size
+ end
+ end
+
+ end
+
+ class RDF
+ class Channel; include ImageFaviconModel; end
+ class Item; include ImageItemModel; end
+ end
+
+end
diff --git a/trunk/lib/rss/itunes.rb b/trunk/lib/rss/itunes.rb
new file mode 100644
index 0000000000..f95ca7aa2e
--- /dev/null
+++ b/trunk/lib/rss/itunes.rb
@@ -0,0 +1,410 @@
+require 'rss/2.0'
+
+module RSS
+ ITUNES_PREFIX = 'itunes'
+ ITUNES_URI = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
+
+ Rss.install_ns(ITUNES_PREFIX, ITUNES_URI)
+
+ module ITunesModelUtils
+ include Utils
+
+ def def_class_accessor(klass, name, type, *args)
+ normalized_name = name.gsub(/-/, "_")
+ full_name = "#{ITUNES_PREFIX}_#{normalized_name}"
+ klass_name = "ITunes#{Utils.to_class_name(normalized_name)}"
+
+ case type
+ when :element, :attribute
+ klass::ELEMENTS << full_name
+ def_element_class_accessor(klass, name, full_name, klass_name, *args)
+ when :elements
+ klass::ELEMENTS << full_name
+ def_elements_class_accessor(klass, name, full_name, klass_name, *args)
+ else
+ klass.install_must_call_validator(ITUNES_PREFIX, ITUNES_URI)
+ klass.install_text_element(normalized_name, ITUNES_URI, "?",
+ full_name, type, name)
+ end
+ end
+
+ def def_element_class_accessor(klass, name, full_name, klass_name,
+ recommended_attribute_name=nil)
+ klass.install_have_child_element(name, ITUNES_PREFIX, "?", full_name)
+ end
+
+ def def_elements_class_accessor(klass, name, full_name, klass_name,
+ plural_name, recommended_attribute_name=nil)
+ full_plural_name = "#{ITUNES_PREFIX}_#{plural_name}"
+ klass.install_have_children_element(name, ITUNES_PREFIX, "*",
+ full_name, full_plural_name)
+ end
+ end
+
+ module ITunesBaseModel
+ extend ITunesModelUtils
+
+ ELEMENTS = []
+
+ ELEMENT_INFOS = [["author"],
+ ["block", :yes_other],
+ ["explicit", :yes_clean_other],
+ ["keywords", :csv],
+ ["subtitle"],
+ ["summary"]]
+ end
+
+ module ITunesChannelModel
+ extend BaseModel
+ extend ITunesModelUtils
+ include ITunesBaseModel
+
+ ELEMENTS = []
+
+ class << self
+ def append_features(klass)
+ super
+
+ return if klass.instance_of?(Module)
+ ELEMENT_INFOS.each do |name, type, *additional_infos|
+ def_class_accessor(klass, name, type, *additional_infos)
+ end
+ end
+ end
+
+ ELEMENT_INFOS = [
+ ["category", :elements, "categories", "text"],
+ ["image", :attribute, "href"],
+ ["owner", :element],
+ ["new-feed-url"],
+ ] + ITunesBaseModel::ELEMENT_INFOS
+
+ class ITunesCategory < Element
+ include RSS09
+
+ @tag_name = "category"
+
+ class << self
+ def required_prefix
+ ITUNES_PREFIX
+ end
+
+ def required_uri
+ ITUNES_URI
+ end
+ end
+
+ [
+ ["text", "", true]
+ ].each do |name, uri, required|
+ install_get_attribute(name, uri, required)
+ end
+
+ ITunesCategory = self
+ install_have_children_element("category", ITUNES_URI, "*",
+ "#{ITUNES_PREFIX}_category",
+ "#{ITUNES_PREFIX}_categories")
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.text = args[0]
+ end
+ end
+
+ def full_name
+ tag_name_with_prefix(ITUNES_PREFIX)
+ end
+
+ private
+ def maker_target(categories)
+ if text or !itunes_categories.empty?
+ categories.new_category
+ else
+ nil
+ end
+ end
+
+ def setup_maker_attributes(category)
+ category.text = text if text
+ end
+
+ def setup_maker_elements(category)
+ super(category)
+ itunes_categories.each do |sub_category|
+ sub_category.setup_maker(category)
+ end
+ end
+ end
+
+ class ITunesImage < Element
+ include RSS09
+
+ @tag_name = "image"
+
+ class << self
+ def required_prefix
+ ITUNES_PREFIX
+ end
+
+ def required_uri
+ ITUNES_URI
+ end
+ end
+
+ [
+ ["href", "", true]
+ ].each do |name, uri, required|
+ install_get_attribute(name, uri, required)
+ end
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.href = args[0]
+ end
+ end
+
+ def full_name
+ tag_name_with_prefix(ITUNES_PREFIX)
+ end
+
+ private
+ def maker_target(target)
+ if href
+ target.itunes_image {|image| image}
+ else
+ nil
+ end
+ end
+
+ def setup_maker_attributes(image)
+ image.href = href
+ end
+ end
+
+ class ITunesOwner < Element
+ include RSS09
+
+ @tag_name = "owner"
+
+ class << self
+ def required_prefix
+ ITUNES_PREFIX
+ end
+
+ def required_uri
+ ITUNES_URI
+ end
+ end
+
+ install_must_call_validator(ITUNES_PREFIX, ITUNES_URI)
+ [
+ ["name"],
+ ["email"],
+ ].each do |name,|
+ ITunesBaseModel::ELEMENT_INFOS << name
+ install_text_element(name, ITUNES_URI, nil, "#{ITUNES_PREFIX}_#{name}")
+ end
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.itunes_name = args[0]
+ self.itunes_email = args[1]
+ end
+ end
+
+ def full_name
+ tag_name_with_prefix(ITUNES_PREFIX)
+ end
+
+ private
+ def maker_target(target)
+ target.itunes_owner
+ end
+
+ def setup_maker_element(owner)
+ super(owner)
+ owner.itunes_name = itunes_name
+ owner.itunes_email = itunes_email
+ end
+ end
+ end
+
+ module ITunesItemModel
+ extend BaseModel
+ extend ITunesModelUtils
+ include ITunesBaseModel
+
+ class << self
+ def append_features(klass)
+ super
+
+ return if klass.instance_of?(Module)
+ ELEMENT_INFOS.each do |name, type|
+ def_class_accessor(klass, name, type)
+ end
+ end
+ end
+
+ ELEMENT_INFOS = ITunesBaseModel::ELEMENT_INFOS +
+ [["duration", :element, "content"]]
+
+ class ITunesDuration < Element
+ include RSS09
+
+ @tag_name = "duration"
+
+ class << self
+ def required_prefix
+ ITUNES_PREFIX
+ end
+
+ def required_uri
+ ITUNES_URI
+ end
+
+ def parse(duration, do_validate=true)
+ if do_validate and /\A(?:
+ \d?\d:[0-5]\d:[0-5]\d|
+ [0-5]?\d:[0-5]\d
+ )\z/x !~ duration
+ raise ArgumentError,
+ "must be one of HH:MM:SS, H:MM:SS, MM::SS, M:SS: " +
+ duration.inspect
+ end
+
+ components = duration.split(':')
+ components[3..-1] = nil if components.size > 3
+
+ components.unshift("00") until components.size == 3
+
+ components.collect do |component|
+ component.to_i
+ end
+ end
+
+ def construct(hour, minute, second)
+ components = [minute, second]
+ if components.include?(nil)
+ nil
+ else
+ components.unshift(hour) if hour and hour > 0
+ components.collect do |component|
+ "%02d" % component
+ end.join(":")
+ end
+ end
+ end
+
+ content_setup
+ alias_method(:value, :content)
+ remove_method(:content=)
+
+ attr_reader :hour, :minute, :second
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ args = args[0] if args.size == 1 and args[0].is_a?(Array)
+ if args.size == 1
+ self.content = args[0]
+ elsif args.size > 3
+ raise ArgumentError,
+ "must be (do_validate, params), (content), " +
+ "(minute, second), ([minute, second]), " +
+ "(hour, minute, second) or ([hour, minute, second]): " +
+ args.inspect
+ else
+ @second, @minute, @hour = args.reverse
+ update_content
+ end
+ end
+ end
+
+ def content=(value)
+ if value.nil?
+ @content = nil
+ elsif value.is_a?(self.class)
+ self.content = value.content
+ else
+ begin
+ @hour, @minute, @second = self.class.parse(value, @do_validate)
+ rescue ArgumentError
+ raise NotAvailableValueError.new(tag_name, value)
+ end
+ @content = value
+ end
+ end
+ alias_method(:value=, :content=)
+
+ def hour=(hour)
+ @hour = @do_validate ? Integer(hour) : hour.to_i
+ update_content
+ hour
+ end
+
+ def minute=(minute)
+ @minute = @do_validate ? Integer(minute) : minute.to_i
+ update_content
+ minute
+ end
+
+ def second=(second)
+ @second = @do_validate ? Integer(second) : second.to_i
+ update_content
+ second
+ end
+
+ def full_name
+ tag_name_with_prefix(ITUNES_PREFIX)
+ end
+
+ private
+ def update_content
+ @content = self.class.construct(hour, minute, second)
+ end
+
+ def maker_target(target)
+ if @content
+ target.itunes_duration {|duration| duration}
+ else
+ nil
+ end
+ end
+
+ def setup_maker_element(duration)
+ super(duration)
+ duration.content = @content
+ end
+ end
+ end
+
+ class Rss
+ class Channel
+ include ITunesChannelModel
+ class Item; include ITunesItemModel; end
+ end
+ end
+
+ element_infos =
+ ITunesChannelModel::ELEMENT_INFOS + ITunesItemModel::ELEMENT_INFOS
+ element_infos.each do |name, type|
+ case type
+ when :element, :elements, :attribute
+ class_name = Utils.to_class_name(name)
+ BaseListener.install_class_name(ITUNES_URI, name, "ITunes#{class_name}")
+ else
+ accessor_base = "#{ITUNES_PREFIX}_#{name.gsub(/-/, '_')}"
+ BaseListener.install_get_text_element(ITUNES_URI, name, accessor_base)
+ end
+ end
+end
diff --git a/trunk/lib/rss/maker.rb b/trunk/lib/rss/maker.rb
new file mode 100644
index 0000000000..bcba1aaff3
--- /dev/null
+++ b/trunk/lib/rss/maker.rb
@@ -0,0 +1,44 @@
+require "rss/rss"
+
+module RSS
+ module Maker
+ MAKERS = {}
+
+ class << self
+ def make(version, &block)
+ m = maker(version)
+ raise UnsupportedMakerVersionError.new(version) if m.nil?
+ m[:maker].make(m[:version], &block)
+ end
+
+ def maker(version)
+ MAKERS[version]
+ end
+
+ def add_maker(version, normalized_version, maker)
+ MAKERS[version] = {:maker => maker, :version => normalized_version}
+ end
+
+ def versions
+ MAKERS.keys.uniq.sort
+ end
+
+ def makers
+ MAKERS.values.collect {|info| info[:maker]}.uniq
+ end
+ end
+ end
+end
+
+require "rss/maker/1.0"
+require "rss/maker/2.0"
+require "rss/maker/feed"
+require "rss/maker/entry"
+require "rss/maker/content"
+require "rss/maker/dublincore"
+require "rss/maker/slash"
+require "rss/maker/syndication"
+require "rss/maker/taxonomy"
+require "rss/maker/trackback"
+require "rss/maker/image"
+require "rss/maker/itunes"
diff --git a/trunk/lib/rss/maker/0.9.rb b/trunk/lib/rss/maker/0.9.rb
new file mode 100644
index 0000000000..72b14dc977
--- /dev/null
+++ b/trunk/lib/rss/maker/0.9.rb
@@ -0,0 +1,467 @@
+require "rss/0.9"
+
+require "rss/maker/base"
+
+module RSS
+ module Maker
+
+ class RSS09 < RSSBase
+
+ def initialize(feed_version="0.92")
+ super
+ @feed_type = "rss"
+ end
+
+ private
+ def make_feed
+ Rss.new(@feed_version, @version, @encoding, @standalone)
+ end
+
+ def setup_elements(rss)
+ setup_channel(rss)
+ end
+
+ class Channel < ChannelBase
+ def to_feed(rss)
+ channel = Rss::Channel.new
+ set = setup_values(channel)
+ _not_set_required_variables = not_set_required_variables
+ if _not_set_required_variables.empty?
+ rss.channel = channel
+ set_parent(channel, rss)
+ setup_items(rss)
+ setup_image(rss)
+ setup_textinput(rss)
+ setup_other_elements(rss, channel)
+ rss
+ else
+ raise NotSetError.new("maker.channel", _not_set_required_variables)
+ end
+ end
+
+ private
+ def setup_items(rss)
+ @maker.items.to_feed(rss)
+ end
+
+ def setup_image(rss)
+ @maker.image.to_feed(rss)
+ end
+
+ def setup_textinput(rss)
+ @maker.textinput.to_feed(rss)
+ end
+
+ def variables
+ super + ["pubDate"]
+ end
+
+ def required_variable_names
+ %w(link language)
+ end
+
+ def not_set_required_variables
+ vars = super
+ vars << "description" unless description {|d| d.have_required_values?}
+ vars << "title" unless title {|t| t.have_required_values?}
+ vars
+ end
+
+ class SkipDays < SkipDaysBase
+ def to_feed(rss, channel)
+ unless @days.empty?
+ skipDays = Rss::Channel::SkipDays.new
+ channel.skipDays = skipDays
+ set_parent(skipDays, channel)
+ @days.each do |day|
+ day.to_feed(rss, skipDays.days)
+ end
+ end
+ end
+
+ class Day < DayBase
+ def to_feed(rss, days)
+ day = Rss::Channel::SkipDays::Day.new
+ set = setup_values(day)
+ if set
+ days << day
+ set_parent(day, days)
+ setup_other_elements(rss, day)
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(content)
+ end
+ end
+ end
+
+ class SkipHours < SkipHoursBase
+ def to_feed(rss, channel)
+ unless @hours.empty?
+ skipHours = Rss::Channel::SkipHours.new
+ channel.skipHours = skipHours
+ set_parent(skipHours, channel)
+ @hours.each do |hour|
+ hour.to_feed(rss, skipHours.hours)
+ end
+ end
+ end
+
+ class Hour < HourBase
+ def to_feed(rss, hours)
+ hour = Rss::Channel::SkipHours::Hour.new
+ set = setup_values(hour)
+ if set
+ hours << hour
+ set_parent(hour, hours)
+ setup_other_elements(rss, hour)
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(content)
+ end
+ end
+ end
+
+ class Cloud < CloudBase
+ def to_feed(*args)
+ end
+ end
+
+ class Categories < CategoriesBase
+ def to_feed(*args)
+ end
+
+ class Category < CategoryBase
+ end
+ end
+
+ class Links < LinksBase
+ def to_feed(rss, channel)
+ return if @links.empty?
+ @links.first.to_feed(rss, channel)
+ end
+
+ class Link < LinkBase
+ def to_feed(rss, channel)
+ if have_required_values?
+ channel.link = href
+ else
+ raise NotSetError.new("maker.channel.link",
+ not_set_required_variables)
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(href)
+ end
+ end
+ end
+
+ class Authors < AuthorsBase
+ def to_feed(rss, channel)
+ end
+
+ class Author < AuthorBase
+ def to_feed(rss, channel)
+ end
+ end
+ end
+
+ class Contributors < ContributorsBase
+ def to_feed(rss, channel)
+ end
+
+ class Contributor < ContributorBase
+ end
+ end
+
+ class Generator < GeneratorBase
+ def to_feed(rss, channel)
+ end
+ end
+
+ class Copyright < CopyrightBase
+ def to_feed(rss, channel)
+ channel.copyright = content if have_required_values?
+ end
+
+ private
+ def required_variable_names
+ %w(content)
+ end
+ end
+
+ class Description < DescriptionBase
+ def to_feed(rss, channel)
+ channel.description = content if have_required_values?
+ end
+
+ private
+ def required_variable_names
+ %w(content)
+ end
+ end
+
+ class Title < TitleBase
+ def to_feed(rss, channel)
+ channel.title = content if have_required_values?
+ end
+
+ private
+ def required_variable_names
+ %w(content)
+ end
+ end
+ end
+
+ class Image < ImageBase
+ def to_feed(rss)
+ image = Rss::Channel::Image.new
+ set = setup_values(image)
+ if set
+ image.link = link
+ rss.channel.image = image
+ set_parent(image, rss.channel)
+ setup_other_elements(rss, image)
+ elsif required_element?
+ raise NotSetError.new("maker.image", not_set_required_variables)
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(url title link)
+ end
+
+ def required_element?
+ true
+ end
+ end
+
+ class Items < ItemsBase
+ def to_feed(rss)
+ if rss.channel
+ normalize.each do |item|
+ item.to_feed(rss)
+ end
+ setup_other_elements(rss, rss.items)
+ end
+ end
+
+ class Item < ItemBase
+ def to_feed(rss)
+ item = Rss::Channel::Item.new
+ set = setup_values(item)
+ _not_set_required_variables = not_set_required_variables
+ if _not_set_required_variables.empty?
+ rss.items << item
+ set_parent(item, rss.channel)
+ setup_other_elements(rss, item)
+ elsif variable_is_set?
+ raise NotSetError.new("maker.items", _not_set_required_variables)
+ end
+ end
+
+ private
+ def required_variable_names
+ []
+ end
+
+ def not_set_required_variables
+ vars = super
+ if @maker.feed_version == "0.91"
+ vars << "title" unless title {|t| t.have_required_values?}
+ vars << "link" unless link {|l| l.have_required_values?}
+ end
+ vars
+ end
+
+ class Guid < GuidBase
+ def to_feed(*args)
+ end
+ end
+
+ class Enclosure < EnclosureBase
+ def to_feed(*args)
+ end
+ end
+
+ class Source < SourceBase
+ def to_feed(*args)
+ end
+
+ class Authors < AuthorsBase
+ def to_feed(*args)
+ end
+
+ class Author < AuthorBase
+ end
+ end
+
+ class Categories < CategoriesBase
+ def to_feed(*args)
+ end
+
+ class Category < CategoryBase
+ end
+ end
+
+ class Contributors < ContributorsBase
+ def to_feed(*args)
+ end
+
+ class Contributor < ContributorBase
+ end
+ end
+
+ class Generator < GeneratorBase
+ def to_feed(*args)
+ end
+ end
+
+ class Icon < IconBase
+ def to_feed(*args)
+ end
+ end
+
+ class Links < LinksBase
+ def to_feed(*args)
+ end
+
+ class Link < LinkBase
+ end
+ end
+
+ class Logo < LogoBase
+ def to_feed(*args)
+ end
+ end
+
+ class Rights < RightsBase
+ def to_feed(*args)
+ end
+ end
+
+ class Subtitle < SubtitleBase
+ def to_feed(*args)
+ end
+ end
+
+ class Title < TitleBase
+ def to_feed(*args)
+ end
+ end
+ end
+
+ class Categories < CategoriesBase
+ def to_feed(*args)
+ end
+
+ class Category < CategoryBase
+ end
+ end
+
+ class Authors < AuthorsBase
+ def to_feed(*args)
+ end
+
+ class Author < AuthorBase
+ end
+ end
+
+ class Links < LinksBase
+ def to_feed(rss, item)
+ return if @links.empty?
+ @links.first.to_feed(rss, item)
+ end
+
+ class Link < LinkBase
+ def to_feed(rss, item)
+ if have_required_values?
+ item.link = href
+ else
+ raise NotSetError.new("maker.link",
+ not_set_required_variables)
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(href)
+ end
+ end
+ end
+
+ class Contributors < ContributorsBase
+ def to_feed(rss, item)
+ end
+
+ class Contributor < ContributorBase
+ end
+ end
+
+ class Rights < RightsBase
+ def to_feed(rss, item)
+ end
+ end
+
+ class Description < DescriptionBase
+ def to_feed(rss, item)
+ item.description = content if have_required_values?
+ end
+
+ private
+ def required_variable_names
+ %w(content)
+ end
+ end
+
+ class Content < ContentBase
+ def to_feed(rss, item)
+ end
+ end
+
+ class Title < TitleBase
+ def to_feed(rss, item)
+ item.title = content if have_required_values?
+ end
+
+ private
+ def required_variable_names
+ %w(content)
+ end
+ end
+ end
+ end
+
+ class Textinput < TextinputBase
+ def to_feed(rss)
+ textInput = Rss::Channel::TextInput.new
+ set = setup_values(textInput)
+ if set
+ rss.channel.textInput = textInput
+ set_parent(textInput, rss.channel)
+ setup_other_elements(rss, textInput)
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(title description name link)
+ end
+ end
+ end
+
+ add_maker("0.9", "0.92", RSS09)
+ add_maker("0.91", "0.91", RSS09)
+ add_maker("0.92", "0.92", RSS09)
+ add_maker("rss0.91", "0.91", RSS09)
+ add_maker("rss0.92", "0.92", RSS09)
+ end
+end
diff --git a/trunk/lib/rss/maker/1.0.rb b/trunk/lib/rss/maker/1.0.rb
new file mode 100644
index 0000000000..a1e2594f70
--- /dev/null
+++ b/trunk/lib/rss/maker/1.0.rb
@@ -0,0 +1,434 @@
+require "rss/1.0"
+
+require "rss/maker/base"
+
+module RSS
+ module Maker
+
+ class RSS10 < RSSBase
+
+ def initialize(feed_version="1.0")
+ super
+ @feed_type = "rss"
+ end
+
+ private
+ def make_feed
+ RDF.new(@version, @encoding, @standalone)
+ end
+
+ def setup_elements(rss)
+ setup_channel(rss)
+ setup_image(rss)
+ setup_items(rss)
+ setup_textinput(rss)
+ end
+
+ class Channel < ChannelBase
+
+ def to_feed(rss)
+ set_default_values do
+ _not_set_required_variables = not_set_required_variables
+ if _not_set_required_variables.empty?
+ channel = RDF::Channel.new(@about)
+ set = setup_values(channel)
+ channel.dc_dates.clear
+ rss.channel = channel
+ set_parent(channel, rss)
+ setup_items(rss)
+ setup_image(rss)
+ setup_textinput(rss)
+ setup_other_elements(rss, channel)
+ else
+ raise NotSetError.new("maker.channel", _not_set_required_variables)
+ end
+ end
+ end
+
+ private
+ def setup_items(rss)
+ items = RDF::Channel::Items.new
+ seq = items.Seq
+ set_parent(items, seq)
+ target_items = @maker.items.normalize
+ raise NotSetError.new("maker", ["items"]) if target_items.empty?
+ target_items.each do |item|
+ li = RDF::Channel::Items::Seq::Li.new(item.link)
+ seq.lis << li
+ set_parent(li, seq)
+ end
+ rss.channel.items = items
+ set_parent(rss.channel, items)
+ end
+
+ def setup_image(rss)
+ if @maker.image.have_required_values?
+ image = RDF::Channel::Image.new(@maker.image.url)
+ rss.channel.image = image
+ set_parent(image, rss.channel)
+ end
+ end
+
+ def setup_textinput(rss)
+ if @maker.textinput.have_required_values?
+ textinput = RDF::Channel::Textinput.new(@maker.textinput.link)
+ rss.channel.textinput = textinput
+ set_parent(textinput, rss.channel)
+ end
+ end
+
+ def required_variable_names
+ %w(about link)
+ end
+
+ def not_set_required_variables
+ vars = super
+ vars << "description" unless description {|d| d.have_required_values?}
+ vars << "title" unless title {|t| t.have_required_values?}
+ vars
+ end
+
+ class SkipDays < SkipDaysBase
+ def to_feed(*args)
+ end
+
+ class Day < DayBase
+ end
+ end
+
+ class SkipHours < SkipHoursBase
+ def to_feed(*args)
+ end
+
+ class Hour < HourBase
+ end
+ end
+
+ class Cloud < CloudBase
+ def to_feed(*args)
+ end
+ end
+
+ class Categories < CategoriesBase
+ def to_feed(*args)
+ end
+
+ class Category < CategoryBase
+ end
+ end
+
+ class Links < LinksBase
+ def to_feed(rss, channel)
+ return if @links.empty?
+ @links.first.to_feed(rss, channel)
+ end
+
+ class Link < LinkBase
+ def to_feed(rss, channel)
+ if have_required_values?
+ channel.link = href
+ else
+ raise NotSetError.new("maker.channel.link",
+ not_set_required_variables)
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(href)
+ end
+ end
+ end
+
+ class Authors < AuthorsBase
+ def to_feed(rss, channel)
+ end
+
+ class Author < AuthorBase
+ def to_feed(rss, channel)
+ end
+ end
+ end
+
+ class Contributors < ContributorsBase
+ def to_feed(rss, channel)
+ end
+
+ class Contributor < ContributorBase
+ end
+ end
+
+ class Generator < GeneratorBase
+ def to_feed(rss, channel)
+ end
+ end
+
+ class Copyright < CopyrightBase
+ def to_feed(rss, channel)
+ end
+ end
+
+ class Description < DescriptionBase
+ def to_feed(rss, channel)
+ channel.description = content if have_required_values?
+ end
+
+ private
+ def required_variable_names
+ %w(content)
+ end
+ end
+
+ class Title < TitleBase
+ def to_feed(rss, channel)
+ channel.title = content if have_required_values?
+ end
+
+ private
+ def required_variable_names
+ %w(content)
+ end
+ end
+ end
+
+ class Image < ImageBase
+ def to_feed(rss)
+ if @url
+ image = RDF::Image.new(@url)
+ set = setup_values(image)
+ if set
+ rss.image = image
+ set_parent(image, rss)
+ setup_other_elements(rss, image)
+ end
+ end
+ end
+
+ def have_required_values?
+ super and @maker.channel.have_required_values?
+ end
+
+ private
+ def variables
+ super + ["link"]
+ end
+
+ def required_variable_names
+ %w(url title link)
+ end
+ end
+
+ class Items < ItemsBase
+ def to_feed(rss)
+ if rss.channel
+ normalize.each do |item|
+ item.to_feed(rss)
+ end
+ setup_other_elements(rss, rss.items)
+ end
+ end
+
+ class Item < ItemBase
+ def to_feed(rss)
+ set_default_values do
+ item = RDF::Item.new(link)
+ set = setup_values(item)
+ if set
+ item.dc_dates.clear
+ rss.items << item
+ set_parent(item, rss)
+ setup_other_elements(rss, item)
+ elsif !have_required_values?
+ raise NotSetError.new("maker.item", not_set_required_variables)
+ end
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(link)
+ end
+
+ def variables
+ super + %w(link)
+ end
+
+ def not_set_required_variables
+ set_default_values do
+ vars = super
+ vars << "title" unless title {|t| t.have_required_values?}
+ vars
+ end
+ end
+
+ class Guid < GuidBase
+ def to_feed(*args)
+ end
+ end
+
+ class Enclosure < EnclosureBase
+ def to_feed(*args)
+ end
+ end
+
+ class Source < SourceBase
+ def to_feed(*args)
+ end
+
+ class Authors < AuthorsBase
+ def to_feed(*args)
+ end
+
+ class Author < AuthorBase
+ end
+ end
+
+ class Categories < CategoriesBase
+ def to_feed(*args)
+ end
+
+ class Category < CategoryBase
+ end
+ end
+
+ class Contributors < ContributorsBase
+ def to_feed(*args)
+ end
+
+ class Contributor < ContributorBase
+ end
+ end
+
+ class Generator < GeneratorBase
+ def to_feed(*args)
+ end
+ end
+
+ class Icon < IconBase
+ def to_feed(*args)
+ end
+ end
+
+ class Links < LinksBase
+ def to_feed(*args)
+ end
+
+ class Link < LinkBase
+ end
+ end
+
+ class Logo < LogoBase
+ def to_feed(*args)
+ end
+ end
+
+ class Rights < RightsBase
+ def to_feed(*args)
+ end
+ end
+
+ class Subtitle < SubtitleBase
+ def to_feed(*args)
+ end
+ end
+
+ class Title < TitleBase
+ def to_feed(*args)
+ end
+ end
+ end
+
+ class Categories < CategoriesBase
+ def to_feed(*args)
+ end
+
+ class Category < CategoryBase
+ end
+ end
+
+ class Authors < AuthorsBase
+ def to_feed(*args)
+ end
+
+ class Author < AuthorBase
+ end
+ end
+
+ class Links < LinksBase
+ def to_feed(*args)
+ end
+
+ class Link < LinkBase
+ end
+ end
+
+ class Contributors < ContributorsBase
+ def to_feed(rss, item)
+ end
+
+ class Contributor < ContributorBase
+ end
+ end
+
+ class Rights < RightsBase
+ def to_feed(rss, item)
+ end
+ end
+
+ class Description < DescriptionBase
+ def to_feed(rss, item)
+ item.description = content if have_required_values?
+ end
+
+ private
+ def required_variable_names
+ %w(content)
+ end
+ end
+
+ class Content < ContentBase
+ def to_feed(rss, item)
+ end
+ end
+
+ class Title < TitleBase
+ def to_feed(rss, item)
+ item.title = content if have_required_values?
+ end
+
+ private
+ def required_variable_names
+ %w(content)
+ end
+ end
+ end
+ end
+
+ class Textinput < TextinputBase
+ def to_feed(rss)
+ if @link
+ textinput = RDF::Textinput.new(@link)
+ set = setup_values(textinput)
+ if set
+ rss.textinput = textinput
+ set_parent(textinput, rss)
+ setup_other_elements(rss, textinput)
+ end
+ end
+ end
+
+ def have_required_values?
+ super and @maker.channel.have_required_values?
+ end
+
+ private
+ def required_variable_names
+ %w(title description name link)
+ end
+ end
+ end
+
+ add_maker("1.0", "1.0", RSS10)
+ add_maker("rss1.0", "1.0", RSS10)
+ end
+end
diff --git a/trunk/lib/rss/maker/2.0.rb b/trunk/lib/rss/maker/2.0.rb
new file mode 100644
index 0000000000..67d68126ac
--- /dev/null
+++ b/trunk/lib/rss/maker/2.0.rb
@@ -0,0 +1,223 @@
+require "rss/2.0"
+
+require "rss/maker/0.9"
+
+module RSS
+ module Maker
+
+ class RSS20 < RSS09
+
+ def initialize(feed_version="2.0")
+ super
+ end
+
+ class Channel < RSS09::Channel
+
+ private
+ def required_variable_names
+ %w(link)
+ end
+
+ class SkipDays < RSS09::Channel::SkipDays
+ class Day < RSS09::Channel::SkipDays::Day
+ end
+ end
+
+ class SkipHours < RSS09::Channel::SkipHours
+ class Hour < RSS09::Channel::SkipHours::Hour
+ end
+ end
+
+ class Cloud < RSS09::Channel::Cloud
+ def to_feed(rss, channel)
+ cloud = Rss::Channel::Cloud.new
+ set = setup_values(cloud)
+ if set
+ channel.cloud = cloud
+ set_parent(cloud, channel)
+ setup_other_elements(rss, cloud)
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(domain port path registerProcedure protocol)
+ end
+ end
+
+ class Categories < RSS09::Channel::Categories
+ def to_feed(rss, channel)
+ @categories.each do |category|
+ category.to_feed(rss, channel)
+ end
+ end
+
+ class Category < RSS09::Channel::Categories::Category
+ def to_feed(rss, channel)
+ category = Rss::Channel::Category.new
+ set = setup_values(category)
+ if set
+ channel.categories << category
+ set_parent(category, channel)
+ setup_other_elements(rss, category)
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(content)
+ end
+ end
+ end
+
+ class Generator < GeneratorBase
+ def to_feed(rss, channel)
+ channel.generator = content
+ end
+
+ private
+ def required_variable_names
+ %w(content)
+ end
+ end
+ end
+
+ class Image < RSS09::Image
+ private
+ def required_element?
+ false
+ end
+ end
+
+ class Items < RSS09::Items
+ class Item < RSS09::Items::Item
+ private
+ def required_variable_names
+ []
+ end
+
+ def not_set_required_variables
+ vars = super
+ if !title {|t| t.have_required_values?} and
+ !description {|d| d.have_required_values?}
+ vars << "title or description"
+ end
+ vars
+ end
+
+ def variables
+ super + ["pubDate"]
+ end
+
+ class Guid < RSS09::Items::Item::Guid
+ def to_feed(rss, item)
+ guid = Rss::Channel::Item::Guid.new
+ set = setup_values(guid)
+ if set
+ item.guid = guid
+ set_parent(guid, item)
+ setup_other_elements(rss, guid)
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(content)
+ end
+ end
+
+ class Enclosure < RSS09::Items::Item::Enclosure
+ def to_feed(rss, item)
+ enclosure = Rss::Channel::Item::Enclosure.new
+ set = setup_values(enclosure)
+ if set
+ item.enclosure = enclosure
+ set_parent(enclosure, item)
+ setup_other_elements(rss, enclosure)
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(url length type)
+ end
+ end
+
+ class Source < RSS09::Items::Item::Source
+ def to_feed(rss, item)
+ source = Rss::Channel::Item::Source.new
+ set = setup_values(source)
+ if set
+ item.source = source
+ set_parent(source, item)
+ setup_other_elements(rss, source)
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(url content)
+ end
+
+ class Links < RSS09::Items::Item::Source::Links
+ def to_feed(rss, source)
+ return if @links.empty?
+ @links.first.to_feed(rss, source)
+ end
+
+ class Link < RSS09::Items::Item::Source::Links::Link
+ def to_feed(rss, source)
+ source.url = href
+ end
+ end
+ end
+ end
+
+ class Categories < RSS09::Items::Item::Categories
+ def to_feed(rss, item)
+ @categories.each do |category|
+ category.to_feed(rss, item)
+ end
+ end
+
+ class Category < RSS09::Items::Item::Categories::Category
+ def to_feed(rss, item)
+ category = Rss::Channel::Item::Category.new
+ set = setup_values(category)
+ if set
+ item.categories << category
+ set_parent(category, item)
+ setup_other_elements(rss)
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(content)
+ end
+ end
+ end
+
+ class Authors < RSS09::Items::Item::Authors
+ def to_feed(rss, item)
+ return if @authors.empty?
+ @authors.first.to_feed(rss, item)
+ end
+
+ class Author < RSS09::Items::Item::Authors::Author
+ def to_feed(rss, item)
+ item.author = name
+ end
+ end
+ end
+ end
+ end
+
+ class Textinput < RSS09::Textinput
+ end
+ end
+
+ add_maker("2.0", "2.0", RSS20)
+ add_maker("rss2.0", "2.0", RSS20)
+ end
+end
diff --git a/trunk/lib/rss/maker/atom.rb b/trunk/lib/rss/maker/atom.rb
new file mode 100644
index 0000000000..fd3198cd9e
--- /dev/null
+++ b/trunk/lib/rss/maker/atom.rb
@@ -0,0 +1,172 @@
+require "rss/atom"
+
+require "rss/maker/base"
+
+module RSS
+ module Maker
+ module AtomPersons
+ module_function
+ def def_atom_persons(klass, name, maker_name, plural=nil)
+ plural ||= "#{name}s"
+ klass_name = Utils.to_class_name(name)
+ plural_klass_name = Utils.to_class_name(plural)
+
+ klass.class_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ class #{plural_klass_name} < #{plural_klass_name}Base
+ class #{klass_name} < #{klass_name}Base
+ def to_feed(feed, current)
+ #{name} = feed.class::#{klass_name}.new
+ set = setup_values(#{name})
+ unless set
+ raise NotSetError.new(#{maker_name.dump},
+ not_set_required_variables)
+ end
+ current.#{plural} << #{name}
+ set_parent(#{name}, current)
+ setup_other_elements(#{name})
+ end
+
+ private
+ def required_variable_names
+ %w(name)
+ end
+ end
+ end
+EOC
+ end
+ end
+
+ module AtomTextConstruct
+ class << self
+ def def_atom_text_construct(klass, name, maker_name, klass_name=nil,
+ atom_klass_name=nil)
+ klass_name ||= Utils.to_class_name(name)
+ atom_klass_name ||= Utils.to_class_name(name)
+
+ klass.class_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ class #{klass_name} < #{klass_name}Base
+ include #{self.name}
+ def to_feed(feed, current)
+ #{name} = current.class::#{atom_klass_name}.new
+ if setup_values(#{name})
+ current.#{name} = #{name}
+ set_parent(#{name}, current)
+ setup_other_elements(feed)
+ elsif variable_is_set?
+ raise NotSetError.new(#{maker_name.dump},
+ not_set_required_variables)
+ end
+ end
+ end
+ EOC
+ end
+ end
+
+ private
+ def required_variable_names
+ if type == "xhtml"
+ %w(xml_content)
+ else
+ %w(content)
+ end
+ end
+
+ def variables
+ if type == "xhtml"
+ super + %w(xhtml)
+ else
+ super
+ end
+ end
+ end
+
+ module AtomCategory
+ def to_feed(feed, current)
+ category = feed.class::Category.new
+ set = setup_values(category)
+ if set
+ current.categories << category
+ set_parent(category, current)
+ setup_other_elements(feed)
+ else
+ raise NotSetError.new(self.class.not_set_name,
+ not_set_required_variables)
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(term)
+ end
+
+ def variables
+ super + ["term", "scheme"]
+ end
+ end
+
+ module AtomLink
+ def to_feed(feed, current)
+ link = feed.class::Link.new
+ set = setup_values(link)
+ if set
+ current.links << link
+ set_parent(link, current)
+ setup_other_elements(feed)
+ else
+ raise NotSetError.new(self.class.not_set_name,
+ not_set_required_variables)
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(href)
+ end
+ end
+
+ module AtomGenerator
+ def to_feed(feed, current)
+ generator = current.class::Generator.new
+ if setup_values(generator)
+ current.generator = generator
+ set_parent(generator, current)
+ setup_other_elements(feed)
+ elsif variable_is_set?
+ raise NotSetError.new(self.class.not_set_name,
+ not_set_required_variables)
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(content)
+ end
+ end
+
+ module AtomLogo
+ def to_feed(feed, current)
+ logo = current.class::Logo.new
+ class << logo
+ alias_method(:uri=, :content=)
+ end
+ set = setup_values(logo)
+ class << logo
+ remove_method(:uri=)
+ end
+ if set
+ current.logo = logo
+ set_parent(logo, current)
+ setup_other_elements(feed)
+ elsif variable_is_set?
+ raise NotSetError.new(self.class.not_set_name,
+ not_set_required_variables)
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(uri)
+ end
+ end
+ end
+end
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
diff --git a/trunk/lib/rss/maker/content.rb b/trunk/lib/rss/maker/content.rb
new file mode 100644
index 0000000000..46c4911f73
--- /dev/null
+++ b/trunk/lib/rss/maker/content.rb
@@ -0,0 +1,21 @@
+require 'rss/content'
+require 'rss/maker/1.0'
+require 'rss/maker/2.0'
+
+module RSS
+ module Maker
+ module ContentModel
+ def self.append_features(klass)
+ super
+
+ ::RSS::ContentModel::ELEMENTS.each do |name|
+ klass.def_other_element(name)
+ end
+ end
+ end
+
+ class ItemsBase
+ class ItemBase; include ContentModel; end
+ end
+ end
+end
diff --git a/trunk/lib/rss/maker/dublincore.rb b/trunk/lib/rss/maker/dublincore.rb
new file mode 100644
index 0000000000..ff4813fe19
--- /dev/null
+++ b/trunk/lib/rss/maker/dublincore.rb
@@ -0,0 +1,124 @@
+require 'rss/dublincore'
+require 'rss/maker/1.0'
+
+module RSS
+ module Maker
+ module DublinCoreModel
+ def self.append_features(klass)
+ super
+
+ ::RSS::DublinCoreModel::ELEMENT_NAME_INFOS.each do |name, plural_name|
+ plural_name ||= "#{name}s"
+ full_name = "#{RSS::DC_PREFIX}_#{name}"
+ full_plural_name = "#{RSS::DC_PREFIX}_#{plural_name}"
+ klass_name = Utils.to_class_name(name)
+ plural_klass_name = "DublinCore#{Utils.to_class_name(plural_name)}"
+ full_plural_klass_name = "self.class::#{plural_klass_name}"
+ full_klass_name = "#{full_plural_klass_name}::#{klass_name}"
+ klass.def_classed_elements(full_name, "value", plural_klass_name,
+ full_plural_name, name)
+ klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ def new_#{full_name}(value=nil)
+ _#{full_name} = #{full_plural_name}.new_#{name}
+ _#{full_name}.value = value
+ if block_given?
+ yield _#{full_name}
+ else
+ _#{full_name}
+ end
+ end
+ EOC
+ end
+
+ klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ # For backward compatibility
+ alias #{DC_PREFIX}_rightses #{DC_PREFIX}_rights_list
+ EOC
+ end
+
+ ::RSS::DublinCoreModel::ELEMENT_NAME_INFOS.each do |name, plural_name|
+ plural_name ||= "#{name}s"
+ full_name ||= "#{DC_PREFIX}_#{name}"
+ full_plural_name ||= "#{DC_PREFIX}_#{plural_name}"
+ klass_name = Utils.to_class_name(name)
+ full_klass_name = "DublinCore#{klass_name}"
+ plural_klass_name = "DublinCore#{Utils.to_class_name(plural_name)}"
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ class #{plural_klass_name}Base < Base
+ def_array_element(#{name.dump}, #{full_plural_name.dump},
+ #{full_klass_name.dump})
+
+ class #{full_klass_name}Base < Base
+ attr_accessor :value
+ add_need_initialize_variable("value")
+ alias_method(:content, :value)
+ alias_method(:content=, :value=)
+
+ def have_required_values?
+ @value
+ end
+
+ def to_feed(feed, current)
+ if value and current.respond_to?(:#{full_name})
+ new_item = current.class::#{full_klass_name}.new(value)
+ current.#{full_plural_name} << new_item
+ end
+ end
+ end
+ #{klass_name}Base = #{full_klass_name}Base
+ end
+ EOC
+ end
+
+ def self.install_dublin_core(klass)
+ ::RSS::DublinCoreModel::ELEMENT_NAME_INFOS.each do |name, plural_name|
+ plural_name ||= "#{name}s"
+ klass_name = Utils.to_class_name(name)
+ full_klass_name = "DublinCore#{klass_name}"
+ plural_klass_name = "DublinCore#{Utils.to_class_name(plural_name)}"
+ klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ class #{plural_klass_name} < #{plural_klass_name}Base
+ class #{full_klass_name} < #{full_klass_name}Base
+ end
+ #{klass_name} = #{full_klass_name}
+ end
+EOC
+ end
+ end
+ end
+
+ class ChannelBase
+ include DublinCoreModel
+ end
+
+ class ImageBase; include DublinCoreModel; end
+ class ItemsBase
+ class ItemBase
+ include DublinCoreModel
+ end
+ end
+ class TextinputBase; include DublinCoreModel; end
+
+ makers.each do |maker|
+ maker.module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ class Channel
+ DublinCoreModel.install_dublin_core(self)
+ end
+
+ class Image
+ DublinCoreModel.install_dublin_core(self)
+ end
+
+ class Items
+ class Item
+ DublinCoreModel.install_dublin_core(self)
+ end
+ end
+
+ class Textinput
+ DublinCoreModel.install_dublin_core(self)
+ end
+ EOC
+ end
+ end
+end
diff --git a/trunk/lib/rss/maker/entry.rb b/trunk/lib/rss/maker/entry.rb
new file mode 100644
index 0000000000..edaa31ec06
--- /dev/null
+++ b/trunk/lib/rss/maker/entry.rb
@@ -0,0 +1,163 @@
+require "rss/maker/atom"
+require "rss/maker/feed"
+
+module RSS
+ module Maker
+ module Atom
+ class Entry < RSSBase
+ def initialize(feed_version="1.0")
+ super
+ @feed_type = "atom"
+ @feed_subtype = "entry"
+ end
+
+ private
+ def make_feed
+ ::RSS::Atom::Entry.new(@version, @encoding, @standalone)
+ end
+
+ def setup_elements(entry)
+ setup_items(entry)
+ end
+
+ class Channel < ChannelBase
+ class SkipDays < SkipDaysBase
+ class Day < DayBase
+ end
+ end
+
+ class SkipHours < SkipHoursBase
+ class Hour < HourBase
+ end
+ end
+
+ class Cloud < CloudBase
+ end
+
+ Categories = Feed::Channel::Categories
+ Links = Feed::Channel::Links
+ Authors = Feed::Channel::Authors
+ Contributors = Feed::Channel::Contributors
+
+ class Generator < GeneratorBase
+ include AtomGenerator
+
+ def self.not_set_name
+ "maker.channel.generator"
+ end
+ end
+
+ Copyright = Feed::Channel::Copyright
+
+ class Description < DescriptionBase
+ end
+
+ Title = Feed::Channel::Title
+ end
+
+ class Image < ImageBase
+ end
+
+ class Items < ItemsBase
+ def to_feed(entry)
+ (normalize.first || Item.new(@maker)).to_feed(entry)
+ end
+
+ class Item < ItemBase
+ def to_feed(entry)
+ set_default_values do
+ setup_values(entry)
+ entry.dc_dates.clear
+ setup_other_elements(entry)
+ unless have_required_values?
+ raise NotSetError.new("maker.item", not_set_required_variables)
+ end
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(id updated)
+ end
+
+ def variables
+ super + ["updated"]
+ end
+
+ def variable_is_set?
+ super or !authors.empty?
+ end
+
+ def not_set_required_variables
+ set_default_values do
+ vars = super
+ if authors.all? {|author| !author.have_required_values?}
+ vars << "author"
+ end
+ vars << "title" unless title {|t| t.have_required_values?}
+ vars
+ end
+ end
+
+ def _set_default_values(&block)
+ keep = {
+ :authors => authors.to_a.dup,
+ :contributors => contributors.to_a.dup,
+ :categories => categories.to_a.dup,
+ :id => id,
+ :links => links.to_a.dup,
+ :rights => @rights,
+ :title => @title,
+ :updated => updated,
+ }
+ authors.replace(@maker.channel.authors) if keep[:authors].empty?
+ if keep[:contributors].empty?
+ contributors.replace(@maker.channel.contributors)
+ end
+ if keep[:categories].empty?
+ categories.replace(@maker.channel.categories)
+ end
+ self.id ||= link || @maker.channel.id
+ links.replace(@maker.channel.links) if keep[:links].empty?
+ unless keep[:rights].variable_is_set?
+ @maker.channel.rights {|r| @rights = r}
+ end
+ unless keep[:title].variable_is_set?
+ @maker.channel.title {|t| @title = t}
+ end
+ self.updated ||= @maker.channel.updated
+ super(&block)
+ ensure
+ authors.replace(keep[:authors])
+ contributors.replace(keep[:contributors])
+ categories.replace(keep[:categories])
+ links.replace(keep[:links])
+ self.id = keep[:id]
+ @rights = keep[:rights]
+ @title = keep[:title]
+ self.updated = keep[:prev_updated]
+ end
+
+ Guid = Feed::Items::Item::Guid
+ Enclosure = Feed::Items::Item::Enclosure
+ Source = Feed::Items::Item::Source
+ Categories = Feed::Items::Item::Categories
+ Authors = Feed::Items::Item::Authors
+ Contributors = Feed::Items::Item::Contributors
+ Links = Feed::Items::Item::Links
+ Rights = Feed::Items::Item::Rights
+ Description = Feed::Items::Item::Description
+ Title = Feed::Items::Item::Title
+ Content = Feed::Items::Item::Content
+ end
+ end
+
+ class Textinput < TextinputBase
+ end
+ end
+ end
+
+ add_maker("atom:entry", "1.0", Atom::Entry)
+ add_maker("atom1.0:entry", "1.0", Atom::Entry)
+ end
+end
diff --git a/trunk/lib/rss/maker/feed.rb b/trunk/lib/rss/maker/feed.rb
new file mode 100644
index 0000000000..3a30ad4287
--- /dev/null
+++ b/trunk/lib/rss/maker/feed.rb
@@ -0,0 +1,429 @@
+require "rss/maker/atom"
+
+module RSS
+ module Maker
+ module Atom
+ class Feed < RSSBase
+ def initialize(feed_version="1.0")
+ super
+ @feed_type = "atom"
+ @feed_subtype = "feed"
+ end
+
+ private
+ def make_feed
+ ::RSS::Atom::Feed.new(@version, @encoding, @standalone)
+ end
+
+ def setup_elements(feed)
+ setup_channel(feed)
+ setup_image(feed)
+ setup_items(feed)
+ end
+
+ class Channel < ChannelBase
+ def to_feed(feed)
+ set_default_values do
+ setup_values(feed)
+ feed.dc_dates.clear
+ setup_other_elements(feed)
+ if image_favicon.about
+ icon = feed.class::Icon.new
+ icon.content = image_favicon.about
+ feed.icon = icon
+ end
+ unless have_required_values?
+ raise NotSetError.new("maker.channel",
+ not_set_required_variables)
+ end
+ end
+ end
+
+ def have_required_values?
+ super and
+ (!authors.empty? or
+ @maker.items.any? {|item| !item.authors.empty?})
+ end
+
+ private
+ def required_variable_names
+ %w(id updated)
+ end
+
+ def variables
+ super + %w(id updated)
+ end
+
+ def variable_is_set?
+ super or !authors.empty?
+ end
+
+ def not_set_required_variables
+ vars = super
+ if authors.empty? and
+ @maker.items.all? {|item| item.author.to_s.empty?}
+ vars << "author"
+ end
+ vars << "title" unless title {|t| t.have_required_values?}
+ vars
+ end
+
+ def _set_default_values(&block)
+ keep = {
+ :id => id,
+ :updated => updated,
+ }
+ self.id ||= about
+ self.updated ||= dc_date
+ super(&block)
+ ensure
+ self.id = keep[:id]
+ self.updated = keep[:updated]
+ end
+
+ class SkipDays < SkipDaysBase
+ def to_feed(*args)
+ end
+
+ class Day < DayBase
+ end
+ end
+
+ class SkipHours < SkipHoursBase
+ def to_feed(*args)
+ end
+
+ class Hour < HourBase
+ end
+ end
+
+ class Cloud < CloudBase
+ def to_feed(*args)
+ end
+ end
+
+ class Categories < CategoriesBase
+ class Category < CategoryBase
+ include AtomCategory
+
+ def self.not_set_name
+ "maker.channel.category"
+ end
+ end
+ end
+
+ class Links < LinksBase
+ class Link < LinkBase
+ include AtomLink
+
+ def self.not_set_name
+ "maker.channel.link"
+ end
+ end
+ end
+
+ AtomPersons.def_atom_persons(self, "author", "maker.channel.author")
+ AtomPersons.def_atom_persons(self, "contributor",
+ "maker.channel.contributor")
+
+ class Generator < GeneratorBase
+ include AtomGenerator
+
+ def self.not_set_name
+ "maker.channel.generator"
+ end
+ end
+
+ AtomTextConstruct.def_atom_text_construct(self, "rights",
+ "maker.channel.copyright",
+ "Copyright")
+ AtomTextConstruct.def_atom_text_construct(self, "subtitle",
+ "maker.channel.description",
+ "Description")
+ AtomTextConstruct.def_atom_text_construct(self, "title",
+ "maker.channel.title")
+ end
+
+ class Image < ImageBase
+ def to_feed(feed)
+ logo = feed.class::Logo.new
+ class << logo
+ alias_method(:url=, :content=)
+ end
+ set = setup_values(logo)
+ class << logo
+ remove_method(:url=)
+ end
+ if set
+ feed.logo = logo
+ set_parent(logo, feed)
+ setup_other_elements(feed, logo)
+ elsif variable_is_set?
+ raise NotSetError.new("maker.image", not_set_required_variables)
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(url)
+ end
+ end
+
+ class Items < ItemsBase
+ def to_feed(feed)
+ normalize.each do |item|
+ item.to_feed(feed)
+ end
+ setup_other_elements(feed, feed.entries)
+ end
+
+ class Item < ItemBase
+ def to_feed(feed)
+ set_default_values do
+ entry = feed.class::Entry.new
+ set = setup_values(entry)
+ setup_other_elements(feed, entry)
+ if set
+ feed.entries << entry
+ set_parent(entry, feed)
+ elsif variable_is_set?
+ raise NotSetError.new("maker.item", not_set_required_variables)
+ end
+ end
+ end
+
+ def have_required_values?
+ set_default_values do
+ super and title {|t| t.have_required_values?}
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(id updated)
+ end
+
+ def variables
+ super + ["updated"]
+ end
+
+ def not_set_required_variables
+ vars = super
+ vars << "title" unless title {|t| t.have_required_values?}
+ vars
+ end
+
+ def _set_default_values(&block)
+ keep = {
+ :id => id,
+ :updated => updated,
+ }
+ self.id ||= link
+ self.updated ||= dc_date
+ super(&block)
+ ensure
+ self.id = keep[:id]
+ self.updated = keep[:updated]
+ end
+
+ class Guid < GuidBase
+ def to_feed(feed, current)
+ end
+ end
+
+ class Enclosure < EnclosureBase
+ def to_feed(feed, current)
+ end
+ end
+
+ class Source < SourceBase
+ def to_feed(feed, current)
+ source = current.class::Source.new
+ setup_values(source)
+ current.source = source
+ set_parent(source, current)
+ setup_other_elements(feed, source)
+ current.source = nil if source.to_s == "<source/>"
+ end
+
+ private
+ def required_variable_names
+ []
+ end
+
+ def variables
+ super + ["updated"]
+ end
+
+ AtomPersons.def_atom_persons(self, "author",
+ "maker.item.source.author")
+ AtomPersons.def_atom_persons(self, "contributor",
+ "maker.item.source.contributor")
+
+ class Categories < CategoriesBase
+ class Category < CategoryBase
+ include AtomCategory
+
+ def self.not_set_name
+ "maker.item.source.category"
+ end
+ end
+ end
+
+ class Generator < GeneratorBase
+ include AtomGenerator
+
+ def self.not_set_name
+ "maker.item.source.generator"
+ end
+ end
+
+ class Icon < IconBase
+ def to_feed(feed, current)
+ icon = current.class::Icon.new
+ class << icon
+ alias_method(:url=, :content=)
+ end
+ set = setup_values(icon)
+ class << icon
+ remove_method(:url=)
+ end
+ if set
+ current.icon = icon
+ set_parent(icon, current)
+ setup_other_elements(feed, icon)
+ elsif variable_is_set?
+ raise NotSetError.new("maker.item.source.icon",
+ not_set_required_variables)
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(url)
+ end
+ end
+
+ class Links < LinksBase
+ class Link < LinkBase
+ include AtomLink
+
+ def self.not_set_name
+ "maker.item.source.link"
+ end
+ end
+ end
+
+ class Logo < LogoBase
+ include AtomLogo
+
+ def self.not_set_name
+ "maker.item.source.logo"
+ end
+ end
+
+ maker_name_base = "maker.item.source."
+ maker_name = "#{maker_name_base}rights"
+ AtomTextConstruct.def_atom_text_construct(self, "rights",
+ maker_name)
+ maker_name = "#{maker_name_base}subtitle"
+ AtomTextConstruct.def_atom_text_construct(self, "subtitle",
+ maker_name)
+ maker_name = "#{maker_name_base}title"
+ AtomTextConstruct.def_atom_text_construct(self, "title",
+ maker_name)
+ end
+
+ class Categories < CategoriesBase
+ class Category < CategoryBase
+ include AtomCategory
+
+ def self.not_set_name
+ "maker.item.category"
+ end
+ end
+ end
+
+ AtomPersons.def_atom_persons(self, "author", "maker.item.author")
+ AtomPersons.def_atom_persons(self, "contributor",
+ "maker.item.contributor")
+
+ class Links < LinksBase
+ class Link < LinkBase
+ include AtomLink
+
+ def self.not_set_name
+ "maker.item.link"
+ end
+ end
+ end
+
+ AtomTextConstruct.def_atom_text_construct(self, "rights",
+ "maker.item.rights")
+ AtomTextConstruct.def_atom_text_construct(self, "summary",
+ "maker.item.description",
+ "Description")
+ AtomTextConstruct.def_atom_text_construct(self, "title",
+ "maker.item.title")
+
+ class Content < ContentBase
+ def to_feed(feed, current)
+ content = current.class::Content.new
+ if setup_values(content)
+ content.src = nil if content.src and content.content
+ current.content = content
+ set_parent(content, current)
+ setup_other_elements(feed, content)
+ elsif variable_is_set?
+ raise NotSetError.new("maker.item.content",
+ not_set_required_variables)
+ end
+ end
+
+ alias_method(:xml, :xml_content)
+
+ private
+ def required_variable_names
+ if out_of_line?
+ %w(type)
+ elsif xml_type?
+ %w(xml_content)
+ else
+ %w(content)
+ end
+ end
+
+ def variables
+ if out_of_line?
+ super
+ elsif xml_type?
+ super + %w(xml)
+ else
+ super
+ end
+ end
+
+ def xml_type?
+ _type = type
+ return false if _type.nil?
+ _type == "xhtml" or
+ /(?:\+xml|\/xml)$/i =~ _type or
+ %w(text/xml-external-parsed-entity
+ application/xml-external-parsed-entity
+ application/xml-dtd).include?(_type.downcase)
+ end
+ end
+ end
+ end
+
+ class Textinput < TextinputBase
+ end
+ end
+ end
+
+ add_maker("atom", "1.0", Atom::Feed)
+ add_maker("atom:feed", "1.0", Atom::Feed)
+ add_maker("atom1.0", "1.0", Atom::Feed)
+ add_maker("atom1.0:feed", "1.0", Atom::Feed)
+ end
+end
diff --git a/trunk/lib/rss/maker/image.rb b/trunk/lib/rss/maker/image.rb
new file mode 100644
index 0000000000..b95cf4c714
--- /dev/null
+++ b/trunk/lib/rss/maker/image.rb
@@ -0,0 +1,111 @@
+require 'rss/image'
+require 'rss/maker/1.0'
+require 'rss/maker/dublincore'
+
+module RSS
+ module Maker
+ module ImageItemModel
+ def self.append_features(klass)
+ super
+
+ name = "#{RSS::IMAGE_PREFIX}_item"
+ klass.def_classed_element(name)
+ end
+
+ def self.install_image_item(klass)
+ klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ class ImageItem < ImageItemBase
+ DublinCoreModel.install_dublin_core(self)
+ end
+EOC
+ end
+
+ class ImageItemBase < Base
+ include Maker::DublinCoreModel
+
+ attr_accessor :about, :resource, :image_width, :image_height
+ add_need_initialize_variable("about")
+ add_need_initialize_variable("resource")
+ add_need_initialize_variable("image_width")
+ add_need_initialize_variable("image_height")
+ alias width= image_width=
+ alias width image_width
+ alias height= image_height=
+ alias height image_height
+
+ def have_required_values?
+ @about
+ end
+
+ def to_feed(feed, current)
+ if current.respond_to?(:image_item=) and have_required_values?
+ item = current.class::ImageItem.new
+ setup_values(item)
+ setup_other_elements(item)
+ current.image_item = item
+ end
+ end
+ end
+ end
+
+ module ImageFaviconModel
+ def self.append_features(klass)
+ super
+
+ name = "#{RSS::IMAGE_PREFIX}_favicon"
+ klass.def_classed_element(name)
+ end
+
+ def self.install_image_favicon(klass)
+ klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ class ImageFavicon < ImageFaviconBase
+ DublinCoreModel.install_dublin_core(self)
+ end
+ EOC
+ end
+
+ class ImageFaviconBase < Base
+ include Maker::DublinCoreModel
+
+ attr_accessor :about, :image_size
+ add_need_initialize_variable("about")
+ add_need_initialize_variable("image_size")
+ alias size image_size
+ alias size= image_size=
+
+ def have_required_values?
+ @about and @image_size
+ end
+
+ def to_feed(feed, current)
+ if current.respond_to?(:image_favicon=) and have_required_values?
+ favicon = current.class::ImageFavicon.new
+ setup_values(favicon)
+ setup_other_elements(favicon)
+ current.image_favicon = favicon
+ end
+ end
+ end
+ end
+
+ class ChannelBase; include Maker::ImageFaviconModel; end
+
+ class ItemsBase
+ class ItemBase; include Maker::ImageItemModel; end
+ end
+
+ makers.each do |maker|
+ maker.module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ class Channel
+ ImageFaviconModel.install_image_favicon(self)
+ end
+
+ class Items
+ class Item
+ ImageItemModel.install_image_item(self)
+ end
+ end
+ EOC
+ end
+ end
+end
diff --git a/trunk/lib/rss/maker/itunes.rb b/trunk/lib/rss/maker/itunes.rb
new file mode 100644
index 0000000000..8b7420da3c
--- /dev/null
+++ b/trunk/lib/rss/maker/itunes.rb
@@ -0,0 +1,242 @@
+require 'rss/itunes'
+require 'rss/maker/2.0'
+
+module RSS
+ module Maker
+ module ITunesBaseModel
+ def def_class_accessor(klass, name, type, *args)
+ name = name.gsub(/-/, "_").gsub(/^itunes_/, '')
+ full_name = "#{RSS::ITUNES_PREFIX}_#{name}"
+ case type
+ when nil
+ klass.def_other_element(full_name)
+ when :yes_other
+ def_yes_other_accessor(klass, full_name)
+ when :yes_clean_other
+ def_yes_clean_other_accessor(klass, full_name)
+ when :csv
+ def_csv_accessor(klass, full_name)
+ when :element, :attribute
+ recommended_attribute_name, = *args
+ klass_name = "ITunes#{Utils.to_class_name(name)}"
+ klass.def_classed_element(full_name, klass_name,
+ recommended_attribute_name)
+ when :elements
+ plural_name, recommended_attribute_name = args
+ plural_name ||= "#{name}s"
+ full_plural_name = "#{RSS::ITUNES_PREFIX}_#{plural_name}"
+ klass_name = "ITunes#{Utils.to_class_name(name)}"
+ plural_klass_name = "ITunes#{Utils.to_class_name(plural_name)}"
+ def_elements_class_accessor(klass, name, full_name, full_plural_name,
+ klass_name, plural_klass_name,
+ recommended_attribute_name)
+ end
+ end
+
+ def def_yes_other_accessor(klass, full_name)
+ klass.def_other_element(full_name)
+ klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ def #{full_name}?
+ Utils::YesOther.parse(@#{full_name})
+ end
+ EOC
+ end
+
+ def def_yes_clean_other_accessor(klass, full_name)
+ klass.def_other_element(full_name)
+ klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ def #{full_name}?
+ Utils::YesCleanOther.parse(#{full_name})
+ end
+ EOC
+ end
+
+ def def_csv_accessor(klass, full_name)
+ klass.def_csv_element(full_name)
+ end
+
+ def def_elements_class_accessor(klass, name, full_name, full_plural_name,
+ klass_name, plural_klass_name,
+ recommended_attribute_name=nil)
+ if recommended_attribute_name
+ klass.def_classed_elements(full_name, recommended_attribute_name,
+ plural_klass_name, full_plural_name)
+ else
+ klass.def_classed_element(full_plural_name, plural_klass_name)
+ end
+ klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ def new_#{full_name}(text=nil)
+ #{full_name} = @#{full_plural_name}.new_#{name}
+ #{full_name}.text = text
+ if block_given?
+ yield #{full_name}
+ else
+ #{full_name}
+ end
+ end
+ EOC
+ end
+ end
+
+ module ITunesChannelModel
+ extend ITunesBaseModel
+
+ class << self
+ def append_features(klass)
+ super
+
+ ::RSS::ITunesChannelModel::ELEMENT_INFOS.each do |name, type, *args|
+ def_class_accessor(klass, name, type, *args)
+ end
+ end
+ end
+
+ class ITunesCategoriesBase < Base
+ def_array_element("category", "itunes_categories",
+ "ITunesCategory")
+ class ITunesCategoryBase < Base
+ attr_accessor :text
+ add_need_initialize_variable("text")
+ def_array_element("category", "itunes_categories",
+ "ITunesCategory")
+
+ def have_required_values?
+ text
+ end
+
+ alias_method :to_feed_for_categories, :to_feed
+ def to_feed(feed, current)
+ if text and current.respond_to?(:itunes_category)
+ new_item = current.class::ITunesCategory.new(text)
+ to_feed_for_categories(feed, new_item)
+ current.itunes_categories << new_item
+ end
+ end
+ end
+ end
+
+ class ITunesImageBase < Base
+ add_need_initialize_variable("href")
+ attr_accessor("href")
+
+ def to_feed(feed, current)
+ if @href and current.respond_to?(:itunes_image)
+ current.itunes_image ||= current.class::ITunesImage.new
+ current.itunes_image.href = @href
+ end
+ end
+ end
+
+ class ITunesOwnerBase < Base
+ %w(itunes_name itunes_email).each do |name|
+ add_need_initialize_variable(name)
+ attr_accessor(name)
+ end
+
+ def to_feed(feed, current)
+ if current.respond_to?(:itunes_owner=)
+ _not_set_required_variables = not_set_required_variables
+ if (required_variable_names - _not_set_required_variables).empty?
+ return
+ end
+
+ unless have_required_values?
+ raise NotSetError.new("maker.channel.itunes_owner",
+ _not_set_required_variables)
+ end
+ current.itunes_owner ||= current.class::ITunesOwner.new
+ current.itunes_owner.itunes_name = @itunes_name
+ current.itunes_owner.itunes_email = @itunes_email
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(itunes_name itunes_email)
+ end
+ end
+ end
+
+ module ITunesItemModel
+ extend ITunesBaseModel
+
+ class << self
+ def append_features(klass)
+ super
+
+ ::RSS::ITunesItemModel::ELEMENT_INFOS.each do |name, type, *args|
+ def_class_accessor(klass, name, type, *args)
+ end
+ end
+ end
+
+ class ITunesDurationBase < Base
+ attr_reader :content
+ add_need_initialize_variable("content")
+
+ %w(hour minute second).each do |name|
+ attr_reader(name)
+ add_need_initialize_variable(name, 0)
+ end
+
+ def content=(content)
+ if content.nil?
+ @hour, @minute, @second, @content = nil
+ else
+ @hour, @minute, @second =
+ ::RSS::ITunesItemModel::ITunesDuration.parse(content)
+ @content = content
+ end
+ end
+
+ def hour=(hour)
+ @hour = Integer(hour)
+ update_content
+ end
+
+ def minute=(minute)
+ @minute = Integer(minute)
+ update_content
+ end
+
+ def second=(second)
+ @second = Integer(second)
+ update_content
+ end
+
+ def to_feed(feed, current)
+ if @content and current.respond_to?(:itunes_duration=)
+ current.itunes_duration ||= current.class::ITunesDuration.new
+ current.itunes_duration.content = @content
+ end
+ end
+
+ private
+ def update_content
+ components = [@hour, @minute, @second]
+ @content =
+ ::RSS::ITunesItemModel::ITunesDuration.construct(*components)
+ end
+ end
+ end
+
+ class ChannelBase
+ include Maker::ITunesChannelModel
+ class ITunesCategories < ITunesCategoriesBase
+ class ITunesCategory < ITunesCategoryBase
+ ITunesCategory = self
+ end
+ end
+
+ class ITunesImage < ITunesImageBase; end
+ class ITunesOwner < ITunesOwnerBase; end
+ end
+
+ class ItemsBase
+ class ItemBase
+ include Maker::ITunesItemModel
+ class ITunesDuration < ITunesDurationBase; end
+ end
+ end
+ end
+end
diff --git a/trunk/lib/rss/maker/slash.rb b/trunk/lib/rss/maker/slash.rb
new file mode 100644
index 0000000000..27adef3832
--- /dev/null
+++ b/trunk/lib/rss/maker/slash.rb
@@ -0,0 +1,33 @@
+require 'rss/slash'
+require 'rss/maker/1.0'
+
+module RSS
+ module Maker
+ module SlashModel
+ def self.append_features(klass)
+ super
+
+ ::RSS::SlashModel::ELEMENT_INFOS.each do |name, type|
+ full_name = "#{RSS::SLASH_PREFIX}_#{name}"
+ case type
+ when :csv_integer
+ klass.def_csv_element(full_name, :integer)
+ else
+ klass.def_other_element(full_name)
+ end
+ end
+
+ klass.module_eval do
+ alias_method(:slash_hit_parades, :slash_hit_parade)
+ alias_method(:slash_hit_parades=, :slash_hit_parade=)
+ end
+ end
+ end
+
+ class ItemsBase
+ class ItemBase
+ include SlashModel
+ end
+ end
+ end
+end
diff --git a/trunk/lib/rss/maker/syndication.rb b/trunk/lib/rss/maker/syndication.rb
new file mode 100644
index 0000000000..b81230457c
--- /dev/null
+++ b/trunk/lib/rss/maker/syndication.rb
@@ -0,0 +1,18 @@
+require 'rss/syndication'
+require 'rss/maker/1.0'
+
+module RSS
+ module Maker
+ module SyndicationModel
+ def self.append_features(klass)
+ super
+
+ ::RSS::SyndicationModel::ELEMENTS.each do |name|
+ klass.def_other_element(name)
+ end
+ end
+ end
+
+ class ChannelBase; include SyndicationModel; end
+ end
+end
diff --git a/trunk/lib/rss/maker/taxonomy.rb b/trunk/lib/rss/maker/taxonomy.rb
new file mode 100644
index 0000000000..211603840f
--- /dev/null
+++ b/trunk/lib/rss/maker/taxonomy.rb
@@ -0,0 +1,118 @@
+require 'rss/taxonomy'
+require 'rss/maker/1.0'
+require 'rss/maker/dublincore'
+
+module RSS
+ module Maker
+ module TaxonomyTopicsModel
+ def self.append_features(klass)
+ super
+
+ klass.def_classed_element("#{RSS::TAXO_PREFIX}_topics",
+ "TaxonomyTopics")
+ end
+
+ def self.install_taxo_topics(klass)
+ klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ class TaxonomyTopics < TaxonomyTopicsBase
+ def to_feed(feed, current)
+ if current.respond_to?(:taxo_topics)
+ topics = current.class::TaxonomyTopics.new
+ bag = topics.Bag
+ @resources.each do |resource|
+ bag.lis << RDF::Bag::Li.new(resource)
+ end
+ current.taxo_topics = topics
+ end
+ end
+ end
+EOC
+ end
+
+ class TaxonomyTopicsBase < Base
+ attr_reader :resources
+ def_array_element("resource")
+ remove_method :new_resource
+ end
+ end
+
+ module TaxonomyTopicModel
+ def self.append_features(klass)
+ super
+
+ class_name = "TaxonomyTopics"
+ klass.def_classed_elements("#{TAXO_PREFIX}_topic", "value", class_name)
+ end
+
+ def self.install_taxo_topic(klass)
+ klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ class TaxonomyTopics < TaxonomyTopicsBase
+ class TaxonomyTopic < TaxonomyTopicBase
+ DublinCoreModel.install_dublin_core(self)
+ TaxonomyTopicsModel.install_taxo_topics(self)
+
+ def to_feed(feed, current)
+ if current.respond_to?(:taxo_topics)
+ topic = current.class::TaxonomyTopic.new(value)
+ topic.taxo_link = value
+ taxo_topics.to_feed(feed, topic) if taxo_topics
+ current.taxo_topics << topic
+ setup_other_elements(feed, topic)
+ end
+ end
+ end
+ end
+EOC
+ end
+
+ class TaxonomyTopicsBase < Base
+ def_array_element("topic", nil, "TaxonomyTopic")
+ alias_method(:new_taxo_topic, :new_topic) # For backward compatibility
+
+ class TaxonomyTopicBase < Base
+ include DublinCoreModel
+ include TaxonomyTopicsModel
+
+ attr_accessor :value
+ add_need_initialize_variable("value")
+ alias_method(:taxo_link, :value)
+ alias_method(:taxo_link=, :value=)
+
+ def have_required_values?
+ @value
+ end
+ end
+ end
+ end
+
+ class RSSBase
+ include TaxonomyTopicModel
+ end
+
+ class ChannelBase
+ include TaxonomyTopicsModel
+ end
+
+ class ItemsBase
+ class ItemBase
+ include TaxonomyTopicsModel
+ end
+ end
+
+ makers.each do |maker|
+ maker.module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ TaxonomyTopicModel.install_taxo_topic(self)
+
+ class Channel
+ TaxonomyTopicsModel.install_taxo_topics(self)
+ end
+
+ class Items
+ class Item
+ TaxonomyTopicsModel.install_taxo_topics(self)
+ end
+ end
+ EOC
+ end
+ end
+end
diff --git a/trunk/lib/rss/maker/trackback.rb b/trunk/lib/rss/maker/trackback.rb
new file mode 100644
index 0000000000..278fe53ebe
--- /dev/null
+++ b/trunk/lib/rss/maker/trackback.rb
@@ -0,0 +1,61 @@
+require 'rss/trackback'
+require 'rss/maker/1.0'
+require 'rss/maker/2.0'
+
+module RSS
+ module Maker
+ module TrackBackModel
+ def self.append_features(klass)
+ super
+
+ klass.def_other_element("#{RSS::TRACKBACK_PREFIX}_ping")
+ klass.def_classed_elements("#{RSS::TRACKBACK_PREFIX}_about", "value",
+ "TrackBackAbouts")
+ end
+
+ class TrackBackAboutsBase < Base
+ def_array_element("about", nil, "TrackBackAbout")
+
+ class TrackBackAboutBase < Base
+ attr_accessor :value
+ add_need_initialize_variable("value")
+
+ alias_method(:resource, :value)
+ alias_method(:resource=, :value=)
+ alias_method(:content, :value)
+ alias_method(:content=, :value=)
+
+ def have_required_values?
+ @value
+ end
+
+ def to_feed(feed, current)
+ if current.respond_to?(:trackback_abouts) and have_required_values?
+ about = current.class::TrackBackAbout.new
+ setup_values(about)
+ setup_other_elements(about)
+ current.trackback_abouts << about
+ end
+ end
+ end
+ end
+ end
+
+ class ItemsBase
+ class ItemBase; include TrackBackModel; end
+ end
+
+ makers.each do |maker|
+ maker.module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ class Items
+ class Item
+ class TrackBackAbouts < TrackBackAboutsBase
+ class TrackBackAbout < TrackBackAboutBase
+ end
+ end
+ end
+ end
+ EOC
+ end
+ end
+end
diff --git a/trunk/lib/rss/parser.rb b/trunk/lib/rss/parser.rb
new file mode 100644
index 0000000000..9b28f0fa8a
--- /dev/null
+++ b/trunk/lib/rss/parser.rb
@@ -0,0 +1,551 @@
+require "forwardable"
+require "open-uri"
+
+require "rss/rss"
+require "rss/xml"
+
+module RSS
+
+ class NotWellFormedError < Error
+ attr_reader :line, :element
+
+ # Create a new NotWellFormedError for an error at +line+
+ # in +element+. If a block is given the return value of
+ # the block ends up in the error message.
+ def initialize(line=nil, element=nil)
+ message = "This is not well formed XML"
+ if element or line
+ message << "\nerror occurred"
+ message << " in #{element}" if element
+ message << " at about #{line} line" if line
+ end
+ message << "\n#{yield}" if block_given?
+ super(message)
+ end
+ end
+
+ class XMLParserNotFound < Error
+ def initialize
+ super("available XML parser was not found in " <<
+ "#{AVAILABLE_PARSER_LIBRARIES.inspect}.")
+ end
+ end
+
+ class NotValidXMLParser < Error
+ def initialize(parser)
+ super("#{parser} is not an available XML parser. " <<
+ "Available XML parser" <<
+ (AVAILABLE_PARSERS.size > 1 ? "s are " : " is ") <<
+ "#{AVAILABLE_PARSERS.inspect}.")
+ end
+ end
+
+ class NSError < InvalidRSSError
+ attr_reader :tag, :prefix, :uri
+ def initialize(tag, prefix, require_uri)
+ @tag, @prefix, @uri = tag, prefix, require_uri
+ super("prefix <#{prefix}> doesn't associate uri " <<
+ "<#{require_uri}> in tag <#{tag}>")
+ end
+ end
+
+ class Parser
+
+ extend Forwardable
+
+ class << self
+
+ @@default_parser = nil
+
+ def default_parser
+ @@default_parser || AVAILABLE_PARSERS.first
+ end
+
+ # Set @@default_parser to new_value if it is one of the
+ # available parsers. Else raise NotValidXMLParser error.
+ def default_parser=(new_value)
+ if AVAILABLE_PARSERS.include?(new_value)
+ @@default_parser = new_value
+ else
+ raise NotValidXMLParser.new(new_value)
+ end
+ end
+
+ def parse(rss, do_validate=true, ignore_unknown_element=true,
+ parser_class=default_parser)
+ parser = new(rss, parser_class)
+ parser.do_validate = do_validate
+ parser.ignore_unknown_element = ignore_unknown_element
+ parser.parse
+ end
+ end
+
+ def_delegators(:@parser, :parse, :rss,
+ :ignore_unknown_element,
+ :ignore_unknown_element=, :do_validate,
+ :do_validate=)
+
+ def initialize(rss, parser_class=self.class.default_parser)
+ @parser = parser_class.new(normalize_rss(rss))
+ end
+
+ private
+
+ # Try to get the XML associated with +rss+.
+ # Return +rss+ if it already looks like XML, or treat it as a URI,
+ # or a file to get the XML,
+ def normalize_rss(rss)
+ return rss if maybe_xml?(rss)
+
+ uri = to_uri(rss)
+
+ if uri.respond_to?(:read)
+ uri.read
+ elsif !rss.tainted? and File.readable?(rss)
+ File.open(rss) {|f| f.read}
+ else
+ rss
+ end
+ end
+
+ # maybe_xml? tests if source is a string that looks like XML.
+ def maybe_xml?(source)
+ source.is_a?(String) and /</ =~ source
+ end
+
+ # Attempt to convert rss to a URI, but just return it if
+ # there's a ::URI::Error
+ def to_uri(rss)
+ return rss if rss.is_a?(::URI::Generic)
+
+ begin
+ ::URI.parse(rss)
+ rescue ::URI::Error
+ rss
+ end
+ end
+ end
+
+ class BaseParser
+
+ class << self
+ def raise_for_undefined_entity?
+ listener.raise_for_undefined_entity?
+ end
+ end
+
+ def initialize(rss)
+ @listener = self.class.listener.new
+ @rss = rss
+ end
+
+ def rss
+ @listener.rss
+ end
+
+ def ignore_unknown_element
+ @listener.ignore_unknown_element
+ end
+
+ def ignore_unknown_element=(new_value)
+ @listener.ignore_unknown_element = new_value
+ end
+
+ def do_validate
+ @listener.do_validate
+ end
+
+ def do_validate=(new_value)
+ @listener.do_validate = new_value
+ end
+
+ def parse
+ if @listener.rss.nil?
+ _parse
+ end
+ @listener.rss
+ end
+
+ end
+
+ class BaseListener
+
+ extend Utils
+
+ class << self
+
+ @@accessor_bases = {}
+ @@registered_uris = {}
+ @@class_names = {}
+
+ # return the setter for the uri, tag_name pair, or nil.
+ def setter(uri, tag_name)
+ _getter = getter(uri, tag_name)
+ if _getter
+ "#{_getter}="
+ else
+ nil
+ end
+ end
+
+ def getter(uri, tag_name)
+ (@@accessor_bases[uri] || {})[tag_name]
+ end
+
+ # return the tag_names for setters associated with uri
+ def available_tags(uri)
+ (@@accessor_bases[uri] || {}).keys
+ end
+
+ # register uri against this name.
+ def register_uri(uri, name)
+ @@registered_uris[name] ||= {}
+ @@registered_uris[name][uri] = nil
+ end
+
+ # test if this uri is registered against this name
+ def uri_registered?(uri, name)
+ @@registered_uris[name].has_key?(uri)
+ end
+
+ # record class_name for the supplied uri and tag_name
+ def install_class_name(uri, tag_name, class_name)
+ @@class_names[uri] ||= {}
+ @@class_names[uri][tag_name] = class_name
+ end
+
+ # retrieve class_name for the supplied uri and tag_name
+ # If it doesn't exist, capitalize the tag_name
+ def class_name(uri, tag_name)
+ name = (@@class_names[uri] || {})[tag_name]
+ return name if name
+
+ tag_name = tag_name.gsub(/[_\-]([a-z]?)/) {$1.upcase}
+ tag_name[0, 1].upcase + tag_name[1..-1]
+ end
+
+ def install_get_text_element(uri, name, accessor_base)
+ install_accessor_base(uri, name, accessor_base)
+ def_get_text_element(uri, name, *get_file_and_line_from_caller(1))
+ end
+
+ def raise_for_undefined_entity?
+ true
+ end
+
+ private
+ # set the accessor for the uri, tag_name pair
+ def install_accessor_base(uri, tag_name, accessor_base)
+ @@accessor_bases[uri] ||= {}
+ @@accessor_bases[uri][tag_name] = accessor_base.chomp("=")
+ end
+
+ def def_get_text_element(uri, element_name, file, line)
+ register_uri(uri, element_name)
+ method_name = "start_#{element_name}"
+ unless private_method_defined?(method_name)
+ define_method(method_name) do |name, prefix, attrs, ns|
+ uri = _ns(ns, prefix)
+ if self.class.uri_registered?(uri, element_name)
+ start_get_text_element(name, prefix, ns, uri)
+ else
+ start_else_element(name, prefix, attrs, ns)
+ end
+ end
+ private(method_name)
+ end
+ end
+ end
+ end
+
+ module ListenerMixin
+ attr_reader :rss
+
+ attr_accessor :ignore_unknown_element
+ attr_accessor :do_validate
+
+ def initialize
+ @rss = nil
+ @ignore_unknown_element = true
+ @do_validate = true
+ @ns_stack = [{"xml" => :xml}]
+ @tag_stack = [[]]
+ @text_stack = ['']
+ @proc_stack = []
+ @last_element = nil
+ @version = @encoding = @standalone = nil
+ @xml_stylesheets = []
+ @xml_child_mode = false
+ @xml_element = nil
+ @last_xml_element = nil
+ end
+
+ # set instance vars for version, encoding, standalone
+ def xmldecl(version, encoding, standalone)
+ @version, @encoding, @standalone = version, encoding, standalone
+ end
+
+ def instruction(name, content)
+ if name == "xml-stylesheet"
+ params = parse_pi_content(content)
+ if params.has_key?("href")
+ @xml_stylesheets << XMLStyleSheet.new(params)
+ end
+ end
+ end
+
+ def tag_start(name, attributes)
+ @text_stack.push('')
+
+ ns = @ns_stack.last.dup
+ attrs = {}
+ attributes.each do |n, v|
+ if /\Axmlns(?:\z|:)/ =~ n
+ ns[$POSTMATCH] = v
+ else
+ attrs[n] = v
+ end
+ end
+ @ns_stack.push(ns)
+
+ prefix, local = split_name(name)
+ @tag_stack.last.push([_ns(ns, prefix), local])
+ @tag_stack.push([])
+ if @xml_child_mode
+ previous = @last_xml_element
+ element_attrs = attributes.dup
+ unless previous
+ ns.each do |ns_prefix, value|
+ next if ns_prefix == "xml"
+ key = ns_prefix.empty? ? "xmlns" : "xmlns:#{ns_prefix}"
+ element_attrs[key] ||= value
+ end
+ end
+ next_element = XML::Element.new(local,
+ prefix.empty? ? nil : prefix,
+ _ns(ns, prefix),
+ element_attrs)
+ previous << next_element if previous
+ @last_xml_element = next_element
+ pr = Proc.new do |text, tags|
+ if previous
+ @last_xml_element = previous
+ else
+ @xml_element = @last_xml_element
+ @last_xml_element = nil
+ end
+ end
+ @proc_stack.push(pr)
+ else
+ if @rss.nil? and respond_to?("initial_start_#{local}", true)
+ __send__("initial_start_#{local}", local, prefix, attrs, ns.dup)
+ elsif respond_to?("start_#{local}", true)
+ __send__("start_#{local}", local, prefix, attrs, ns.dup)
+ else
+ start_else_element(local, prefix, attrs, ns.dup)
+ end
+ end
+ end
+
+ def tag_end(name)
+ if DEBUG
+ p "end tag #{name}"
+ p @tag_stack
+ end
+ text = @text_stack.pop
+ tags = @tag_stack.pop
+ pr = @proc_stack.pop
+ pr.call(text, tags) unless pr.nil?
+ @ns_stack.pop
+ end
+
+ def text(data)
+ if @xml_child_mode
+ @last_xml_element << data if @last_xml_element
+ else
+ @text_stack.last << data
+ end
+ end
+
+ private
+ def _ns(ns, prefix)
+ ns.fetch(prefix, "")
+ end
+
+ CONTENT_PATTERN = /\s*([^=]+)=(["'])([^\2]+?)\2/
+ # Extract the first name="value" pair from content.
+ # Works with single quotes according to the constant
+ # CONTENT_PATTERN. Return a Hash.
+ def parse_pi_content(content)
+ params = {}
+ content.scan(CONTENT_PATTERN) do |name, quote, value|
+ params[name] = value
+ end
+ params
+ end
+
+ def start_else_element(local, prefix, attrs, ns)
+ class_name = self.class.class_name(_ns(ns, prefix), local)
+ current_class = @last_element.class
+ if known_class?(current_class, class_name)
+ next_class = current_class.const_get(class_name)
+ start_have_something_element(local, prefix, attrs, ns, next_class)
+ else
+ if !@do_validate or @ignore_unknown_element
+ @proc_stack.push(nil)
+ else
+ parent = "ROOT ELEMENT???"
+ if current_class.tag_name
+ parent = current_class.tag_name
+ end
+ raise NotExpectedTagError.new(local, _ns(ns, prefix), parent)
+ end
+ end
+ end
+
+ if Module.method(:const_defined?).arity == -1
+ def known_class?(target_class, class_name)
+ class_name and
+ (target_class.const_defined?(class_name, false) or
+ target_class.constants.include?(class_name.to_sym))
+ end
+ else
+ def known_class?(target_class, class_name)
+ class_name and
+ (target_class.const_defined?(class_name) or
+ target_class.constants.include?(class_name))
+ end
+ end
+
+ NAMESPLIT = /^(?:([\w:][-\w\d.]*):)?([\w:][-\w\d.]*)/
+ def split_name(name)
+ name =~ NAMESPLIT
+ [$1 || '', $2]
+ end
+
+ def check_ns(tag_name, prefix, ns, require_uri)
+ unless _ns(ns, prefix) == require_uri
+ if @do_validate
+ raise NSError.new(tag_name, prefix, require_uri)
+ else
+ # Force bind required URI with prefix
+ @ns_stack.last[prefix] = require_uri
+ end
+ end
+ end
+
+ def start_get_text_element(tag_name, prefix, ns, required_uri)
+ pr = Proc.new do |text, tags|
+ setter = self.class.setter(required_uri, tag_name)
+ if @last_element.respond_to?(setter)
+ if @do_validate
+ getter = self.class.getter(required_uri, tag_name)
+ if @last_element.__send__(getter)
+ raise TooMuchTagError.new(tag_name, @last_element.tag_name)
+ end
+ end
+ @last_element.__send__(setter, text.to_s)
+ else
+ if @do_validate and !@ignore_unknown_element
+ raise NotExpectedTagError.new(tag_name, _ns(ns, prefix),
+ @last_element.tag_name)
+ end
+ end
+ end
+ @proc_stack.push(pr)
+ end
+
+ def start_have_something_element(tag_name, prefix, attrs, ns, klass)
+ check_ns(tag_name, prefix, ns, klass.required_uri)
+ attributes = collect_attributes(tag_name, prefix, attrs, ns, klass)
+ @proc_stack.push(setup_next_element(tag_name, klass, attributes))
+ end
+
+ def collect_attributes(tag_name, prefix, attrs, ns, klass)
+ attributes = {}
+ klass.get_attributes.each do |a_name, a_uri, required, element_name|
+ if a_uri.is_a?(String) or !a_uri.respond_to?(:include?)
+ a_uri = [a_uri]
+ end
+ unless a_uri == [""]
+ for prefix, uri in ns
+ if a_uri.include?(uri)
+ val = attrs["#{prefix}:#{a_name}"]
+ break if val
+ end
+ end
+ end
+ if val.nil? and a_uri.include?("")
+ val = attrs[a_name]
+ end
+
+ if @do_validate and required and val.nil?
+ unless a_uri.include?("")
+ for prefix, uri in ns
+ if a_uri.include?(uri)
+ a_name = "#{prefix}:#{a_name}"
+ end
+ end
+ end
+ raise MissingAttributeError.new(tag_name, a_name)
+ end
+
+ attributes[a_name] = val
+ end
+ attributes
+ end
+
+ def setup_next_element(tag_name, klass, attributes)
+ previous = @last_element
+ next_element = klass.new(@do_validate, attributes)
+ previous.set_next_element(tag_name, next_element)
+ @last_element = next_element
+ @last_element.parent = previous if klass.need_parent?
+ @xml_child_mode = @last_element.have_xml_content?
+
+ Proc.new do |text, tags|
+ p(@last_element.class) if DEBUG
+ if @xml_child_mode
+ @last_element.content = @xml_element.to_s
+ xml_setter = @last_element.class.xml_setter
+ @last_element.__send__(xml_setter, @xml_element)
+ @xml_element = nil
+ @xml_child_mode = false
+ else
+ if klass.have_content?
+ if @last_element.need_base64_encode?
+ text = text.lstrip.unpack("m").first
+ end
+ @last_element.content = text
+ end
+ end
+ if @do_validate
+ @last_element.validate_for_stream(tags, @ignore_unknown_element)
+ end
+ @last_element = previous
+ end
+ end
+ end
+
+ unless const_defined? :AVAILABLE_PARSER_LIBRARIES
+ AVAILABLE_PARSER_LIBRARIES = [
+ ["rss/xmlparser", :XMLParserParser],
+ ["rss/xmlscanner", :XMLScanParser],
+ ["rss/rexmlparser", :REXMLParser],
+ ]
+ end
+
+ AVAILABLE_PARSERS = []
+
+ AVAILABLE_PARSER_LIBRARIES.each do |lib, parser|
+ begin
+ require lib
+ AVAILABLE_PARSERS.push(const_get(parser))
+ rescue LoadError
+ end
+ end
+
+ if AVAILABLE_PARSERS.empty?
+ raise XMLParserNotFound
+ end
+end
diff --git a/trunk/lib/rss/rexmlparser.rb b/trunk/lib/rss/rexmlparser.rb
new file mode 100644
index 0000000000..4dabf59199
--- /dev/null
+++ b/trunk/lib/rss/rexmlparser.rb
@@ -0,0 +1,54 @@
+require "rexml/document"
+require "rexml/streamlistener"
+
+/\A(\d+)\.(\d+)(?:\.\d+)+\z/ =~ REXML::Version
+if ([$1.to_i, $2.to_i] <=> [2, 5]) < 0
+ raise LoadError, "needs REXML 2.5 or later (#{REXML::Version})"
+end
+
+module RSS
+
+ class REXMLParser < BaseParser
+
+ class << self
+ def listener
+ REXMLListener
+ end
+ end
+
+ private
+ def _parse
+ begin
+ REXML::Document.parse_stream(@rss, @listener)
+ rescue RuntimeError => e
+ raise NotWellFormedError.new{e.message}
+ rescue REXML::ParseException => e
+ context = e.context
+ line = context[0] if context
+ raise NotWellFormedError.new(line){e.message}
+ end
+ end
+
+ end
+
+ class REXMLListener < BaseListener
+
+ include REXML::StreamListener
+ include ListenerMixin
+
+ class << self
+ def raise_for_undefined_entity?
+ false
+ end
+ end
+
+ def xmldecl(version, encoding, standalone)
+ super(version, encoding, standalone == "yes")
+ # Encoding is converted to UTF-8 when REXML parse XML.
+ @encoding = 'UTF-8'
+ end
+
+ alias_method(:cdata, :text)
+ end
+
+end
diff --git a/trunk/lib/rss/rss.rb b/trunk/lib/rss/rss.rb
new file mode 100644
index 0000000000..4b943ec55b
--- /dev/null
+++ b/trunk/lib/rss/rss.rb
@@ -0,0 +1,1313 @@
+require "time"
+
+class Time
+ class << self
+ unless respond_to?(:w3cdtf)
+ def w3cdtf(date)
+ if /\A\s*
+ (-?\d+)-(\d\d)-(\d\d)
+ (?:T
+ (\d\d):(\d\d)(?::(\d\d))?
+ (\.\d+)?
+ (Z|[+-]\d\d:\d\d)?)?
+ \s*\z/ix =~ date and (($5 and $8) or (!$5 and !$8))
+ datetime = [$1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i]
+ usec = 0
+ usec = $7.to_f * 1000000 if $7
+ zone = $8
+ if zone
+ off = zone_offset(zone, datetime[0])
+ datetime = apply_offset(*(datetime + [off]))
+ datetime << usec
+ time = Time.utc(*datetime)
+ time.localtime unless zone_utc?(zone)
+ time
+ else
+ datetime << usec
+ Time.local(*datetime)
+ end
+ else
+ raise ArgumentError.new("invalid date: #{date.inspect}")
+ end
+ end
+ end
+ end
+
+ unless method_defined?(:w3cdtf)
+ def w3cdtf
+ if usec.zero?
+ fraction_digits = 0
+ else
+ fraction_digits = Math.log10(usec.to_s.sub(/0*$/, '').to_i).floor + 1
+ end
+ xmlschema(fraction_digits)
+ end
+ end
+end
+
+
+require "English"
+require "rss/utils"
+require "rss/converter"
+require "rss/xml-stylesheet"
+
+module RSS
+
+ VERSION = "0.2.5"
+
+ URI = "http://purl.org/rss/1.0/"
+
+ DEBUG = false
+
+ class Error < StandardError; end
+
+ class OverlappedPrefixError < Error
+ attr_reader :prefix
+ def initialize(prefix)
+ @prefix = prefix
+ end
+ end
+
+ class InvalidRSSError < Error; end
+
+ class MissingTagError < InvalidRSSError
+ attr_reader :tag, :parent
+ def initialize(tag, parent)
+ @tag, @parent = tag, parent
+ super("tag <#{tag}> is missing in tag <#{parent}>")
+ end
+ end
+
+ class TooMuchTagError < InvalidRSSError
+ attr_reader :tag, :parent
+ def initialize(tag, parent)
+ @tag, @parent = tag, parent
+ super("tag <#{tag}> is too much in tag <#{parent}>")
+ end
+ end
+
+ class MissingAttributeError < InvalidRSSError
+ attr_reader :tag, :attribute
+ def initialize(tag, attribute)
+ @tag, @attribute = tag, attribute
+ super("attribute <#{attribute}> is missing in tag <#{tag}>")
+ end
+ end
+
+ class UnknownTagError < InvalidRSSError
+ attr_reader :tag, :uri
+ def initialize(tag, uri)
+ @tag, @uri = tag, uri
+ super("tag <#{tag}> is unknown in namespace specified by uri <#{uri}>")
+ end
+ end
+
+ class NotExpectedTagError < InvalidRSSError
+ attr_reader :tag, :uri, :parent
+ def initialize(tag, uri, parent)
+ @tag, @uri, @parent = tag, uri, parent
+ super("tag <{#{uri}}#{tag}> is not expected in tag <#{parent}>")
+ end
+ end
+ # For backward compatibility :X
+ NotExceptedTagError = NotExpectedTagError
+
+ class NotAvailableValueError < InvalidRSSError
+ attr_reader :tag, :value, :attribute
+ def initialize(tag, value, attribute=nil)
+ @tag, @value, @attribute = tag, value, attribute
+ message = "value <#{value}> of "
+ message << "attribute <#{attribute}> of " if attribute
+ message << "tag <#{tag}> is not available."
+ super(message)
+ end
+ end
+
+ class UnknownConversionMethodError < Error
+ attr_reader :to, :from
+ def initialize(to, from)
+ @to = to
+ @from = from
+ super("can't convert to #{to} from #{from}.")
+ end
+ end
+ # for backward compatibility
+ UnknownConvertMethod = UnknownConversionMethodError
+
+ class ConversionError < Error
+ attr_reader :string, :to, :from
+ def initialize(string, to, from)
+ @string = string
+ @to = to
+ @from = from
+ super("can't convert #{@string} to #{to} from #{from}.")
+ end
+ end
+
+ class NotSetError < Error
+ attr_reader :name, :variables
+ def initialize(name, variables)
+ @name = name
+ @variables = variables
+ super("required variables of #{@name} are not set: #{@variables.join(', ')}")
+ end
+ end
+
+ class UnsupportedMakerVersionError < Error
+ attr_reader :version
+ def initialize(version)
+ @version = version
+ super("Maker doesn't support version: #{@version}")
+ end
+ end
+
+ module BaseModel
+ include Utils
+
+ def install_have_child_element(tag_name, uri, occurs, name=nil, type=nil)
+ name ||= tag_name
+ add_need_initialize_variable(name)
+ install_model(tag_name, uri, occurs, name)
+
+ writer_type, reader_type = type
+ def_corresponded_attr_writer name, writer_type
+ def_corresponded_attr_reader name, reader_type
+ install_element(name) do |n, elem_name|
+ <<-EOC
+ if @#{n}
+ "\#{@#{n}.to_s(need_convert, indent)}"
+ else
+ ''
+ end
+EOC
+ end
+ end
+ alias_method(:install_have_attribute_element, :install_have_child_element)
+
+ def install_have_children_element(tag_name, uri, occurs, name=nil, plural_name=nil)
+ name ||= tag_name
+ plural_name ||= "#{name}s"
+ add_have_children_element(name, plural_name)
+ add_plural_form(name, plural_name)
+ install_model(tag_name, uri, occurs, plural_name, true)
+
+ def_children_accessor(name, plural_name)
+ install_element(name, "s") do |n, elem_name|
+ <<-EOC
+ rv = []
+ @#{n}.each do |x|
+ value = "\#{x.to_s(need_convert, indent)}"
+ rv << value if /\\A\\s*\\z/ !~ value
+ end
+ rv.join("\n")
+EOC
+ end
+ end
+
+ def install_text_element(tag_name, uri, occurs, name=nil, type=nil,
+ disp_name=nil)
+ name ||= tag_name
+ disp_name ||= name
+ self::ELEMENTS << name unless self::ELEMENTS.include?(name)
+ add_need_initialize_variable(name)
+ install_model(tag_name, uri, occurs, name)
+
+ def_corresponded_attr_writer(name, type, disp_name)
+ def_corresponded_attr_reader(name, type || :convert)
+ install_element(name) do |n, elem_name|
+ <<-EOC
+ if respond_to?(:#{n}_content)
+ content = #{n}_content
+ else
+ content = @#{n}
+ end
+ if content
+ rv = "\#{indent}<#{elem_name}>"
+ value = html_escape(content)
+ if need_convert
+ rv << convert(value)
+ else
+ rv << value
+ end
+ rv << "</#{elem_name}>"
+ rv
+ else
+ ''
+ end
+EOC
+ end
+ end
+
+ def install_date_element(tag_name, uri, occurs, name=nil, type=nil, disp_name=nil)
+ name ||= tag_name
+ type ||= :w3cdtf
+ disp_name ||= name
+ self::ELEMENTS << name
+ add_need_initialize_variable(name)
+ install_model(tag_name, uri, occurs, name)
+
+ # accessor
+ convert_attr_reader name
+ date_writer(name, type, disp_name)
+
+ install_element(name) do |n, elem_name|
+ <<-EOC
+ if @#{n}
+ rv = "\#{indent}<#{elem_name}>"
+ value = html_escape(@#{n}.#{type})
+ if need_convert
+ rv << convert(value)
+ else
+ rv << value
+ end
+ rv << "</#{elem_name}>"
+ rv
+ else
+ ''
+ end
+EOC
+ end
+
+ end
+
+ private
+ def install_element(name, postfix="")
+ elem_name = name.sub('_', ':')
+ method_name = "#{name}_element#{postfix}"
+ add_to_element_method(method_name)
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
+ def #{method_name}(need_convert=true, indent='')
+ #{yield(name, elem_name)}
+ end
+ private :#{method_name}
+EOC
+ end
+
+ def inherit_convert_attr_reader(*attrs)
+ attrs.each do |attr|
+ attr = attr.id2name if attr.kind_of?(Integer)
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
+ def #{attr}_without_inherit
+ convert(@#{attr})
+ end
+
+ def #{attr}
+ if @#{attr}
+ #{attr}_without_inherit
+ elsif @parent
+ @parent.#{attr}
+ else
+ nil
+ end
+ end
+EOC
+ end
+ end
+
+ def uri_convert_attr_reader(*attrs)
+ attrs.each do |attr|
+ attr = attr.id2name if attr.kind_of?(Integer)
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
+ def #{attr}_without_base
+ convert(@#{attr})
+ end
+
+ def #{attr}
+ value = #{attr}_without_base
+ return nil if value.nil?
+ if /\\A[a-z][a-z0-9+.\\-]*:/i =~ value
+ value
+ else
+ "\#{base}\#{value}"
+ end
+ end
+EOC
+ end
+ end
+
+ def convert_attr_reader(*attrs)
+ attrs.each do |attr|
+ attr = attr.id2name if attr.kind_of?(Integer)
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
+ def #{attr}
+ convert(@#{attr})
+ end
+EOC
+ end
+ end
+
+ def yes_clean_other_attr_reader(*attrs)
+ attrs.each do |attr|
+ attr = attr.id2name if attr.kind_of?(Integer)
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ attr_reader(:#{attr})
+ def #{attr}?
+ YesCleanOther.parse(@#{attr})
+ end
+ EOC
+ end
+ end
+
+ def yes_other_attr_reader(*attrs)
+ attrs.each do |attr|
+ attr = attr.id2name if attr.kind_of?(Integer)
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ attr_reader(:#{attr})
+ def #{attr}?
+ Utils::YesOther.parse(@#{attr})
+ end
+ EOC
+ end
+ end
+
+ def csv_attr_reader(*attrs)
+ separator = nil
+ if attrs.last.is_a?(Hash)
+ options = attrs.pop
+ separator = options[:separator]
+ end
+ separator ||= ", "
+ attrs.each do |attr|
+ attr = attr.id2name if attr.kind_of?(Integer)
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ attr_reader(:#{attr})
+ def #{attr}_content
+ if @#{attr}.nil?
+ @#{attr}
+ else
+ @#{attr}.join(#{separator.dump})
+ end
+ end
+ EOC
+ end
+ end
+
+ def date_writer(name, type, disp_name=name)
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
+ def #{name}=(new_value)
+ if new_value.nil?
+ @#{name} = new_value
+ elsif new_value.kind_of?(Time)
+ @#{name} = new_value.dup
+ else
+ if @do_validate
+ begin
+ @#{name} = Time.__send__('#{type}', new_value)
+ rescue ArgumentError
+ raise NotAvailableValueError.new('#{disp_name}', new_value)
+ end
+ else
+ @#{name} = nil
+ if /\\A\\s*\\z/ !~ new_value.to_s
+ begin
+ unless Date._parse(new_value, false).empty?
+ @#{name} = Time.parse(new_value)
+ end
+ rescue ArgumentError
+ end
+ end
+ end
+ end
+
+ # Is it need?
+ if @#{name}
+ class << @#{name}
+ undef_method(:to_s)
+ alias_method(:to_s, :#{type})
+ end
+ end
+
+ end
+EOC
+ end
+
+ def integer_writer(name, disp_name=name)
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
+ def #{name}=(new_value)
+ if new_value.nil?
+ @#{name} = new_value
+ else
+ if @do_validate
+ begin
+ @#{name} = Integer(new_value)
+ rescue ArgumentError
+ raise NotAvailableValueError.new('#{disp_name}', new_value)
+ end
+ else
+ @#{name} = new_value.to_i
+ end
+ end
+ end
+EOC
+ end
+
+ def positive_integer_writer(name, disp_name=name)
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
+ def #{name}=(new_value)
+ if new_value.nil?
+ @#{name} = new_value
+ else
+ if @do_validate
+ begin
+ tmp = Integer(new_value)
+ raise ArgumentError if tmp <= 0
+ @#{name} = tmp
+ rescue ArgumentError
+ raise NotAvailableValueError.new('#{disp_name}', new_value)
+ end
+ else
+ @#{name} = new_value.to_i
+ end
+ end
+ end
+EOC
+ end
+
+ def boolean_writer(name, disp_name=name)
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
+ def #{name}=(new_value)
+ if new_value.nil?
+ @#{name} = new_value
+ else
+ if @do_validate and
+ ![true, false, "true", "false"].include?(new_value)
+ raise NotAvailableValueError.new('#{disp_name}', new_value)
+ end
+ if [true, false].include?(new_value)
+ @#{name} = new_value
+ else
+ @#{name} = new_value == "true"
+ end
+ end
+ end
+EOC
+ end
+
+ def text_type_writer(name, disp_name=name)
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
+ def #{name}=(new_value)
+ if @do_validate and
+ !["text", "html", "xhtml", nil].include?(new_value)
+ raise NotAvailableValueError.new('#{disp_name}', new_value)
+ end
+ @#{name} = new_value
+ end
+EOC
+ end
+
+ def content_writer(name, disp_name=name)
+ klass_name = "self.class::#{Utils.to_class_name(name)}"
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
+ def #{name}=(new_value)
+ if new_value.is_a?(#{klass_name})
+ @#{name} = new_value
+ else
+ @#{name} = #{klass_name}.new
+ @#{name}.content = new_value
+ end
+ end
+EOC
+ end
+
+ def yes_clean_other_writer(name, disp_name=name)
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ def #{name}=(value)
+ value = (value ? "yes" : "no") if [true, false].include?(value)
+ @#{name} = value
+ end
+ EOC
+ end
+
+ def yes_other_writer(name, disp_name=name)
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ def #{name}=(new_value)
+ if [true, false].include?(new_value)
+ new_value = new_value ? "yes" : "no"
+ end
+ @#{name} = new_value
+ end
+ EOC
+ end
+
+ def csv_writer(name, disp_name=name)
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ def #{name}=(new_value)
+ @#{name} = Utils::CSV.parse(new_value)
+ end
+ EOC
+ end
+
+ def csv_integer_writer(name, disp_name=name)
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ def #{name}=(new_value)
+ @#{name} = Utils::CSV.parse(new_value) {|v| Integer(v)}
+ end
+ EOC
+ end
+
+ def def_children_accessor(accessor_name, plural_name)
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
+ def #{plural_name}
+ @#{accessor_name}
+ end
+
+ def #{accessor_name}(*args)
+ if args.empty?
+ @#{accessor_name}.first
+ else
+ @#{accessor_name}[*args]
+ end
+ end
+
+ def #{accessor_name}=(*args)
+ receiver = self.class.name
+ warn("Warning:\#{caller.first.sub(/:in `.*'\z/, '')}: " \
+ "Don't use `\#{receiver}\##{accessor_name} = XXX'/" \
+ "`\#{receiver}\#set_#{accessor_name}(XXX)'. " \
+ "Those APIs are not sense of Ruby. " \
+ "Use `\#{receiver}\##{plural_name} << XXX' instead of them.")
+ if args.size == 1
+ @#{accessor_name}.push(args[0])
+ else
+ @#{accessor_name}.__send__("[]=", *args)
+ end
+ end
+ alias_method(:set_#{accessor_name}, :#{accessor_name}=)
+EOC
+ end
+ end
+
+ module SetupMaker
+ def setup_maker(maker)
+ target = maker_target(maker)
+ unless target.nil?
+ setup_maker_attributes(target)
+ setup_maker_element(target)
+ setup_maker_elements(target)
+ end
+ end
+
+ private
+ def maker_target(maker)
+ nil
+ end
+
+ def setup_maker_attributes(target)
+ end
+
+ def setup_maker_element(target)
+ self.class.need_initialize_variables.each do |var|
+ value = __send__(var)
+ next if value.nil?
+ if value.respond_to?("setup_maker") and
+ !not_need_to_call_setup_maker_variables.include?(var)
+ value.setup_maker(target)
+ else
+ setter = "#{var}="
+ if target.respond_to?(setter)
+ target.__send__(setter, value)
+ end
+ end
+ end
+ end
+
+ def not_need_to_call_setup_maker_variables
+ []
+ end
+
+ def setup_maker_elements(parent)
+ self.class.have_children_elements.each do |name, plural_name|
+ if parent.respond_to?(plural_name)
+ target = parent.__send__(plural_name)
+ __send__(plural_name).each do |elem|
+ elem.setup_maker(target)
+ end
+ end
+ end
+ end
+ end
+
+ class Element
+ extend BaseModel
+ include Utils
+ extend Utils::InheritedReader
+ include SetupMaker
+
+ INDENT = " "
+
+ MUST_CALL_VALIDATORS = {}
+ MODELS = []
+ GET_ATTRIBUTES = []
+ HAVE_CHILDREN_ELEMENTS = []
+ TO_ELEMENT_METHODS = []
+ NEED_INITIALIZE_VARIABLES = []
+ PLURAL_FORMS = {}
+
+ class << self
+ def must_call_validators
+ inherited_hash_reader("MUST_CALL_VALIDATORS")
+ end
+ def models
+ inherited_array_reader("MODELS")
+ end
+ def get_attributes
+ inherited_array_reader("GET_ATTRIBUTES")
+ end
+ def have_children_elements
+ inherited_array_reader("HAVE_CHILDREN_ELEMENTS")
+ end
+ def to_element_methods
+ inherited_array_reader("TO_ELEMENT_METHODS")
+ end
+ def need_initialize_variables
+ inherited_array_reader("NEED_INITIALIZE_VARIABLES")
+ end
+ def plural_forms
+ inherited_hash_reader("PLURAL_FORMS")
+ end
+
+ def inherited_base
+ ::RSS::Element
+ end
+
+ def inherited(klass)
+ klass.const_set("MUST_CALL_VALIDATORS", {})
+ klass.const_set("MODELS", [])
+ klass.const_set("GET_ATTRIBUTES", [])
+ klass.const_set("HAVE_CHILDREN_ELEMENTS", [])
+ klass.const_set("TO_ELEMENT_METHODS", [])
+ klass.const_set("NEED_INITIALIZE_VARIABLES", [])
+ klass.const_set("PLURAL_FORMS", {})
+
+ tag_name = klass.name.split(/::/).last
+ tag_name[0, 1] = tag_name[0, 1].downcase
+ klass.instance_variable_set("@tag_name", tag_name)
+ klass.instance_variable_set("@have_content", false)
+ end
+
+ def install_must_call_validator(prefix, uri)
+ self::MUST_CALL_VALIDATORS[uri] = prefix
+ end
+
+ def install_model(tag, uri, occurs=nil, getter=nil, plural=false)
+ getter ||= tag
+ if m = self::MODELS.find {|t, u, o, g, p| t == tag and u == uri}
+ m[2] = occurs
+ else
+ self::MODELS << [tag, uri, occurs, getter, plural]
+ end
+ end
+
+ def install_get_attribute(name, uri, required=true,
+ type=nil, disp_name=nil,
+ element_name=nil)
+ disp_name ||= name
+ element_name ||= name
+ writer_type, reader_type = type
+ def_corresponded_attr_writer name, writer_type, disp_name
+ def_corresponded_attr_reader name, reader_type
+ if type == :boolean and /^is/ =~ name
+ alias_method "#{$POSTMATCH}?", name
+ end
+ self::GET_ATTRIBUTES << [name, uri, required, element_name]
+ add_need_initialize_variable(disp_name)
+ end
+
+ def def_corresponded_attr_writer(name, type=nil, disp_name=nil)
+ disp_name ||= name
+ case type
+ when :integer
+ integer_writer name, disp_name
+ when :positive_integer
+ positive_integer_writer name, disp_name
+ when :boolean
+ boolean_writer name, disp_name
+ when :w3cdtf, :rfc822, :rfc2822
+ date_writer name, type, disp_name
+ when :text_type
+ text_type_writer name, disp_name
+ when :content
+ content_writer name, disp_name
+ when :yes_clean_other
+ yes_clean_other_writer name, disp_name
+ when :yes_other
+ yes_other_writer name, disp_name
+ when :csv
+ csv_writer name
+ when :csv_integer
+ csv_integer_writer name
+ else
+ attr_writer name
+ end
+ end
+
+ def def_corresponded_attr_reader(name, type=nil)
+ case type
+ when :inherit
+ inherit_convert_attr_reader name
+ when :uri
+ uri_convert_attr_reader name
+ when :yes_clean_other
+ yes_clean_other_attr_reader name
+ when :yes_other
+ yes_other_attr_reader name
+ when :csv
+ csv_attr_reader name
+ when :csv_integer
+ csv_attr_reader name, :separator => ","
+ else
+ convert_attr_reader name
+ end
+ end
+
+ def content_setup(type=nil, disp_name=nil)
+ writer_type, reader_type = type
+ def_corresponded_attr_writer :content, writer_type, disp_name
+ def_corresponded_attr_reader :content, reader_type
+ @have_content = true
+ end
+
+ def have_content?
+ @have_content
+ end
+
+ def add_have_children_element(variable_name, plural_name)
+ self::HAVE_CHILDREN_ELEMENTS << [variable_name, plural_name]
+ end
+
+ def add_to_element_method(method_name)
+ self::TO_ELEMENT_METHODS << method_name
+ end
+
+ def add_need_initialize_variable(variable_name)
+ self::NEED_INITIALIZE_VARIABLES << variable_name
+ end
+
+ def add_plural_form(singular, plural)
+ self::PLURAL_FORMS[singular] = plural
+ end
+
+ def required_prefix
+ nil
+ end
+
+ def required_uri
+ ""
+ end
+
+ def need_parent?
+ false
+ end
+
+ def install_ns(prefix, uri)
+ if self::NSPOOL.has_key?(prefix)
+ raise OverlappedPrefixError.new(prefix)
+ end
+ self::NSPOOL[prefix] = uri
+ end
+
+ def tag_name
+ @tag_name
+ end
+ end
+
+ attr_accessor :parent, :do_validate
+
+ def initialize(do_validate=true, attrs=nil)
+ @parent = nil
+ @converter = nil
+ if attrs.nil? and (do_validate.is_a?(Hash) or do_validate.is_a?(Array))
+ do_validate, attrs = true, do_validate
+ end
+ @do_validate = do_validate
+ initialize_variables(attrs || {})
+ end
+
+ def tag_name
+ self.class.tag_name
+ end
+
+ def full_name
+ tag_name
+ end
+
+ def converter=(converter)
+ @converter = converter
+ targets = children.dup
+ self.class.have_children_elements.each do |variable_name, plural_name|
+ targets.concat(__send__(plural_name))
+ end
+ targets.each do |target|
+ target.converter = converter unless target.nil?
+ end
+ end
+
+ def convert(value)
+ if @converter
+ @converter.convert(value)
+ else
+ value
+ end
+ end
+
+ def valid?(ignore_unknown_element=true)
+ validate(ignore_unknown_element)
+ true
+ rescue RSS::Error
+ false
+ end
+
+ def validate(ignore_unknown_element=true)
+ do_validate = @do_validate
+ @do_validate = true
+ validate_attribute
+ __validate(ignore_unknown_element)
+ ensure
+ @do_validate = do_validate
+ end
+
+ def validate_for_stream(tags, ignore_unknown_element=true)
+ validate_attribute
+ __validate(ignore_unknown_element, tags, false)
+ end
+
+ def to_s(need_convert=true, indent='')
+ if self.class.have_content?
+ return "" if !empty_content? and !content_is_set?
+ rv = tag(indent) do |next_indent|
+ if empty_content?
+ ""
+ else
+ xmled_content
+ end
+ end
+ else
+ rv = tag(indent) do |next_indent|
+ self.class.to_element_methods.collect do |method_name|
+ __send__(method_name, false, next_indent)
+ end
+ end
+ end
+ rv = convert(rv) if need_convert
+ rv
+ end
+
+ def have_xml_content?
+ false
+ end
+
+ def need_base64_encode?
+ false
+ end
+
+ def set_next_element(tag_name, next_element)
+ klass = next_element.class
+ prefix = ""
+ prefix << "#{klass.required_prefix}_" if klass.required_prefix
+ key = "#{prefix}#{tag_name.gsub(/-/, '_')}"
+ if self.class.plural_forms.has_key?(key)
+ ary = __send__("#{self.class.plural_forms[key]}")
+ ary << next_element
+ else
+ __send__("#{key}=", next_element)
+ end
+ end
+
+ protected
+ def have_required_elements?
+ self.class::MODELS.all? do |tag, uri, occurs, getter|
+ if occurs.nil? or occurs == "+"
+ child = __send__(getter)
+ if child.is_a?(Array)
+ children = child
+ children.any? {|c| c.have_required_elements?}
+ else
+ !child.to_s.empty?
+ end
+ else
+ true
+ end
+ end
+ end
+
+ private
+ def initialize_variables(attrs)
+ normalized_attrs = {}
+ attrs.each do |key, value|
+ normalized_attrs[key.to_s] = value
+ end
+ self.class.need_initialize_variables.each do |variable_name|
+ value = normalized_attrs[variable_name.to_s]
+ if value
+ __send__("#{variable_name}=", value)
+ else
+ instance_variable_set("@#{variable_name}", nil)
+ end
+ end
+ initialize_have_children_elements
+ @content = normalized_attrs["content"] if self.class.have_content?
+ end
+
+ def initialize_have_children_elements
+ self.class.have_children_elements.each do |variable_name, plural_name|
+ instance_variable_set("@#{variable_name}", [])
+ end
+ end
+
+ def tag(indent, additional_attrs={}, &block)
+ next_indent = indent + INDENT
+
+ attrs = collect_attrs
+ return "" if attrs.nil?
+
+ return "" unless have_required_elements?
+
+ attrs.update(additional_attrs)
+ start_tag = make_start_tag(indent, next_indent, attrs.dup)
+
+ if block
+ content = block.call(next_indent)
+ else
+ content = []
+ end
+
+ if content.is_a?(String)
+ content = [content]
+ start_tag << ">"
+ end_tag = "</#{full_name}>"
+ else
+ content = content.reject{|x| x.empty?}
+ if content.empty?
+ return "" if attrs.empty?
+ end_tag = "/>"
+ else
+ start_tag << ">\n"
+ end_tag = "\n#{indent}</#{full_name}>"
+ end
+ end
+
+ start_tag + content.join("\n") + end_tag
+ end
+
+ def make_start_tag(indent, next_indent, attrs)
+ start_tag = ["#{indent}<#{full_name}"]
+ unless attrs.empty?
+ start_tag << attrs.collect do |key, value|
+ %Q[#{h key}="#{h value}"]
+ end.join("\n#{next_indent}")
+ end
+ start_tag.join(" ")
+ end
+
+ def collect_attrs
+ attrs = {}
+ _attrs.each do |name, required, alias_name|
+ value = __send__(alias_name || name)
+ return nil if required and value.nil?
+ next if value.nil?
+ return nil if attrs.has_key?(name)
+ attrs[name] = value
+ end
+ attrs
+ end
+
+ def tag_name_with_prefix(prefix)
+ "#{prefix}:#{tag_name}"
+ end
+
+ # For backward compatibility
+ def calc_indent
+ ''
+ end
+
+ def children
+ rv = []
+ self.class.models.each do |name, uri, occurs, getter|
+ value = __send__(getter)
+ next if value.nil?
+ value = [value] unless value.is_a?(Array)
+ value.each do |v|
+ rv << v if v.is_a?(Element)
+ end
+ end
+ rv
+ end
+
+ def _tags
+ rv = []
+ self.class.models.each do |name, uri, occurs, getter, plural|
+ value = __send__(getter)
+ next if value.nil?
+ if plural and value.is_a?(Array)
+ rv.concat([[uri, name]] * value.size)
+ else
+ rv << [uri, name]
+ end
+ end
+ rv
+ end
+
+ def _attrs
+ self.class.get_attributes.collect do |name, uri, required, element_name|
+ [element_name, required, name]
+ end
+ end
+
+ def __validate(ignore_unknown_element, tags=_tags, recursive=true)
+ if recursive
+ children.compact.each do |child|
+ child.validate
+ end
+ end
+ must_call_validators = self.class.must_call_validators
+ tags = tag_filter(tags.dup)
+ p tags if DEBUG
+ must_call_validators.each do |uri, prefix|
+ _validate(ignore_unknown_element, tags[uri], uri)
+ meth = "#{prefix}_validate"
+ if !prefix.empty? and respond_to?(meth, true)
+ __send__(meth, ignore_unknown_element, tags[uri], uri)
+ end
+ end
+ end
+
+ def validate_attribute
+ _attrs.each do |a_name, required, alias_name|
+ value = instance_variable_get("@#{alias_name || a_name}")
+ if required and value.nil?
+ raise MissingAttributeError.new(tag_name, a_name)
+ end
+ __send__("#{alias_name || a_name}=", value)
+ end
+ end
+
+ def _validate(ignore_unknown_element, tags, uri, models=self.class.models)
+ count = 1
+ do_redo = false
+ not_shift = false
+ tag = nil
+ models = models.find_all {|model| model[1] == uri}
+ element_names = models.collect {|model| model[0]}
+ if tags
+ tags_size = tags.size
+ tags = tags.sort_by {|x| element_names.index(x) || tags_size}
+ end
+
+ _tags = tags.dup if tags
+ models.each_with_index do |model, i|
+ name, model_uri, occurs, getter = model
+
+ if DEBUG
+ p "before"
+ p tags
+ p model
+ end
+
+ if not_shift
+ not_shift = false
+ elsif tags
+ tag = tags.shift
+ end
+
+ if DEBUG
+ p "mid"
+ p count
+ end
+
+ case occurs
+ when '?'
+ if count > 2
+ raise TooMuchTagError.new(name, tag_name)
+ else
+ if name == tag
+ do_redo = true
+ else
+ not_shift = true
+ end
+ end
+ when '*'
+ if name == tag
+ do_redo = true
+ else
+ not_shift = true
+ end
+ when '+'
+ if name == tag
+ do_redo = true
+ else
+ if count > 1
+ not_shift = true
+ else
+ raise MissingTagError.new(name, tag_name)
+ end
+ end
+ else
+ if name == tag
+ if models[i+1] and models[i+1][0] != name and
+ tags and tags.first == name
+ raise TooMuchTagError.new(name, tag_name)
+ end
+ else
+ raise MissingTagError.new(name, tag_name)
+ end
+ end
+
+ if DEBUG
+ p "after"
+ p not_shift
+ p do_redo
+ p tag
+ end
+
+ if do_redo
+ do_redo = false
+ count += 1
+ redo
+ else
+ count = 1
+ end
+
+ end
+
+ if !ignore_unknown_element and !tags.nil? and !tags.empty?
+ raise NotExpectedTagError.new(tags.first, uri, tag_name)
+ end
+
+ end
+
+ def tag_filter(tags)
+ rv = {}
+ tags.each do |tag|
+ rv[tag[0]] = [] unless rv.has_key?(tag[0])
+ rv[tag[0]].push(tag[1])
+ end
+ rv
+ end
+
+ def empty_content?
+ false
+ end
+
+ def content_is_set?
+ if have_xml_content?
+ __send__(self.class.xml_getter)
+ else
+ content
+ end
+ end
+
+ def xmled_content
+ if have_xml_content?
+ __send__(self.class.xml_getter).to_s
+ else
+ _content = content
+ _content = [_content].pack("m").delete("\n") if need_base64_encode?
+ h(_content)
+ end
+ end
+ end
+
+ module RootElementMixin
+
+ include XMLStyleSheetMixin
+
+ attr_reader :output_encoding
+ attr_reader :feed_type, :feed_subtype, :feed_version
+ attr_accessor :version, :encoding, :standalone
+ def initialize(feed_version, version=nil, encoding=nil, standalone=nil)
+ super()
+ @feed_type = nil
+ @feed_subtype = nil
+ @feed_version = feed_version
+ @version = version || '1.0'
+ @encoding = encoding
+ @standalone = standalone
+ @output_encoding = nil
+ end
+
+ def feed_info
+ [@feed_type, @feed_version, @feed_subtype]
+ end
+
+ def output_encoding=(enc)
+ @output_encoding = enc
+ self.converter = Converter.new(@output_encoding, @encoding)
+ end
+
+ def setup_maker(maker)
+ maker.version = version
+ maker.encoding = encoding
+ maker.standalone = standalone
+
+ xml_stylesheets.each do |xss|
+ xss.setup_maker(maker)
+ end
+
+ super
+ end
+
+ def to_feed(type, &block)
+ Maker.make(type) do |maker|
+ setup_maker(maker)
+ block.call(maker) if block
+ end
+ end
+
+ def to_rss(type, &block)
+ to_feed("rss#{type}", &block)
+ end
+
+ def to_atom(type, &block)
+ to_feed("atom:#{type}", &block)
+ end
+
+ def to_xml(type=nil, &block)
+ if type.nil? or same_feed_type?(type)
+ to_s
+ else
+ to_feed(type, &block).to_s
+ end
+ end
+
+ private
+ def same_feed_type?(type)
+ if /^(atom|rss)?(\d+\.\d+)?(?::(.+))?$/i =~ type
+ feed_type = ($1 || @feed_type).downcase
+ feed_version = $2 || @feed_version
+ feed_subtype = $3 || @feed_subtype
+ [feed_type, feed_version, feed_subtype] == feed_info
+ else
+ false
+ end
+ end
+
+ def tag(indent, attrs={}, &block)
+ rv = super(indent, ns_declarations.merge(attrs), &block)
+ return rv if rv.empty?
+ "#{xmldecl}#{xml_stylesheet_pi}#{rv}"
+ end
+
+ def xmldecl
+ rv = %Q[<?xml version="#{@version}"]
+ if @output_encoding or @encoding
+ rv << %Q[ encoding="#{@output_encoding or @encoding}"]
+ end
+ rv << %Q[ standalone="yes"] if @standalone
+ rv << "?>\n"
+ rv
+ end
+
+ def ns_declarations
+ decls = {}
+ self.class::NSPOOL.collect do |prefix, uri|
+ prefix = ":#{prefix}" unless prefix.empty?
+ decls["xmlns#{prefix}"] = uri
+ end
+ decls
+ end
+
+ def maker_target(target)
+ target
+ end
+ end
+end
diff --git a/trunk/lib/rss/slash.rb b/trunk/lib/rss/slash.rb
new file mode 100644
index 0000000000..f102413b46
--- /dev/null
+++ b/trunk/lib/rss/slash.rb
@@ -0,0 +1,49 @@
+require 'rss/1.0'
+
+module RSS
+ SLASH_PREFIX = 'slash'
+ SLASH_URI = "http://purl.org/rss/1.0/modules/slash/"
+
+ RDF.install_ns(SLASH_PREFIX, SLASH_URI)
+
+ module SlashModel
+ extend BaseModel
+
+ ELEMENT_INFOS = \
+ [
+ ["section"],
+ ["department"],
+ ["comments", :positive_integer],
+ ["hit_parade", :csv_integer],
+ ]
+
+ class << self
+ def append_features(klass)
+ super
+
+ return if klass.instance_of?(Module)
+ klass.install_must_call_validator(SLASH_PREFIX, SLASH_URI)
+ ELEMENT_INFOS.each do |name, type, *additional_infos|
+ full_name = "#{SLASH_PREFIX}_#{name}"
+ klass.install_text_element(full_name, SLASH_URI, "?",
+ full_name, type, name)
+ end
+
+ klass.module_eval do
+ alias_method(:slash_hit_parades, :slash_hit_parade)
+ undef_method(:slash_hit_parade)
+ alias_method(:slash_hit_parade, :slash_hit_parade_content)
+ end
+ end
+ end
+ end
+
+ class RDF
+ class Item; include SlashModel; end
+ end
+
+ SlashModel::ELEMENT_INFOS.each do |name, type|
+ accessor_base = "#{SLASH_PREFIX}_#{name}"
+ BaseListener.install_get_text_element(SLASH_URI, name, accessor_base)
+ end
+end
diff --git a/trunk/lib/rss/syndication.rb b/trunk/lib/rss/syndication.rb
new file mode 100644
index 0000000000..3eb15429f6
--- /dev/null
+++ b/trunk/lib/rss/syndication.rb
@@ -0,0 +1,67 @@
+require "rss/1.0"
+
+module RSS
+
+ SY_PREFIX = 'sy'
+ SY_URI = "http://purl.org/rss/1.0/modules/syndication/"
+
+ RDF.install_ns(SY_PREFIX, SY_URI)
+
+ module SyndicationModel
+
+ extend BaseModel
+
+ ELEMENTS = []
+
+ def self.append_features(klass)
+ super
+
+ klass.install_must_call_validator(SY_PREFIX, SY_URI)
+ klass.module_eval do
+ [
+ ["updatePeriod"],
+ ["updateFrequency", :positive_integer]
+ ].each do |name, type|
+ install_text_element(name, SY_URI, "?",
+ "#{SY_PREFIX}_#{name}", type,
+ "#{SY_PREFIX}:#{name}")
+ end
+
+ %w(updateBase).each do |name|
+ install_date_element(name, SY_URI, "?",
+ "#{SY_PREFIX}_#{name}", 'w3cdtf',
+ "#{SY_PREFIX}:#{name}")
+ end
+ end
+
+ klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ alias_method(:_sy_updatePeriod=, :sy_updatePeriod=)
+ def sy_updatePeriod=(new_value)
+ new_value = new_value.strip
+ validate_sy_updatePeriod(new_value) if @do_validate
+ self._sy_updatePeriod = new_value
+ end
+ EOC
+ end
+
+ private
+ SY_UPDATEPERIOD_AVAILABLE_VALUES = %w(hourly daily weekly monthly yearly)
+ def validate_sy_updatePeriod(value)
+ unless SY_UPDATEPERIOD_AVAILABLE_VALUES.include?(value)
+ raise NotAvailableValueError.new("updatePeriod", value)
+ end
+ end
+ end
+
+ class RDF
+ class Channel; include SyndicationModel; end
+ end
+
+ prefix_size = SY_PREFIX.size + 1
+ SyndicationModel::ELEMENTS.uniq!
+ SyndicationModel::ELEMENTS.each do |full_name|
+ name = full_name[prefix_size..-1]
+ BaseListener.install_get_text_element(SY_URI, name, full_name)
+ end
+
+end
diff --git a/trunk/lib/rss/taxonomy.rb b/trunk/lib/rss/taxonomy.rb
new file mode 100644
index 0000000000..276f63b05d
--- /dev/null
+++ b/trunk/lib/rss/taxonomy.rb
@@ -0,0 +1,145 @@
+require "rss/1.0"
+require "rss/dublincore"
+
+module RSS
+
+ TAXO_PREFIX = "taxo"
+ TAXO_URI = "http://purl.org/rss/1.0/modules/taxonomy/"
+
+ RDF.install_ns(TAXO_PREFIX, TAXO_URI)
+
+ TAXO_ELEMENTS = []
+
+ %w(link).each do |name|
+ full_name = "#{TAXO_PREFIX}_#{name}"
+ BaseListener.install_get_text_element(TAXO_URI, name, full_name)
+ TAXO_ELEMENTS << "#{TAXO_PREFIX}_#{name}"
+ end
+
+ %w(topic topics).each do |name|
+ class_name = Utils.to_class_name(name)
+ BaseListener.install_class_name(TAXO_URI, name, "Taxonomy#{class_name}")
+ TAXO_ELEMENTS << "#{TAXO_PREFIX}_#{name}"
+ end
+
+ module TaxonomyTopicsModel
+ extend BaseModel
+
+ def self.append_features(klass)
+ super
+
+ klass.install_must_call_validator(TAXO_PREFIX, TAXO_URI)
+ %w(topics).each do |name|
+ klass.install_have_child_element(name, TAXO_URI, "?",
+ "#{TAXO_PREFIX}_#{name}")
+ end
+ end
+
+ class TaxonomyTopics < Element
+ include RSS10
+
+ Bag = ::RSS::RDF::Bag
+
+ class << self
+ def required_prefix
+ TAXO_PREFIX
+ end
+
+ def required_uri
+ TAXO_URI
+ end
+ end
+
+ @tag_name = "topics"
+
+ install_have_child_element("Bag", RDF::URI, nil)
+ install_must_call_validator('rdf', RDF::URI)
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.Bag = args[0]
+ end
+ self.Bag ||= Bag.new
+ end
+
+ def full_name
+ tag_name_with_prefix(TAXO_PREFIX)
+ end
+
+ def maker_target(target)
+ target.taxo_topics
+ end
+
+ def resources
+ if @Bag
+ @Bag.lis.collect do |li|
+ li.resource
+ end
+ else
+ []
+ end
+ end
+ end
+ end
+
+ module TaxonomyTopicModel
+ extend BaseModel
+
+ def self.append_features(klass)
+ super
+ var_name = "#{TAXO_PREFIX}_topic"
+ klass.install_have_children_element("topic", TAXO_URI, "*", var_name)
+ end
+
+ class TaxonomyTopic < Element
+ include RSS10
+
+ include DublinCoreModel
+ include TaxonomyTopicsModel
+
+ class << self
+ def required_prefix
+ TAXO_PREFIX
+ end
+
+ def required_uri
+ TAXO_URI
+ end
+ end
+
+ @tag_name = "topic"
+
+ install_get_attribute("about", ::RSS::RDF::URI, true, nil, nil,
+ "#{RDF::PREFIX}:about")
+ install_text_element("link", TAXO_URI, "?", "#{TAXO_PREFIX}_link")
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.about = args[0]
+ end
+ end
+
+ def full_name
+ tag_name_with_prefix(TAXO_PREFIX)
+ end
+
+ def maker_target(target)
+ target.new_taxo_topic
+ end
+ end
+ end
+
+ class RDF
+ include TaxonomyTopicModel
+ class Channel
+ include TaxonomyTopicsModel
+ end
+ class Item; include TaxonomyTopicsModel; end
+ end
+end
diff --git a/trunk/lib/rss/trackback.rb b/trunk/lib/rss/trackback.rb
new file mode 100644
index 0000000000..ee2491f332
--- /dev/null
+++ b/trunk/lib/rss/trackback.rb
@@ -0,0 +1,288 @@
+require 'rss/1.0'
+require 'rss/2.0'
+
+module RSS
+
+ TRACKBACK_PREFIX = 'trackback'
+ TRACKBACK_URI = 'http://madskills.com/public/xml/rss/module/trackback/'
+
+ RDF.install_ns(TRACKBACK_PREFIX, TRACKBACK_URI)
+ Rss.install_ns(TRACKBACK_PREFIX, TRACKBACK_URI)
+
+ module TrackBackUtils
+ private
+ def trackback_validate(ignore_unknown_element, tags, uri)
+ return if tags.nil?
+ if tags.find {|tag| tag == "about"} and
+ !tags.find {|tag| tag == "ping"}
+ raise MissingTagError.new("#{TRACKBACK_PREFIX}:ping", tag_name)
+ end
+ end
+ end
+
+ module BaseTrackBackModel
+
+ ELEMENTS = %w(ping about)
+
+ def append_features(klass)
+ super
+
+ unless klass.class == Module
+ klass.module_eval {include TrackBackUtils}
+
+ klass.install_must_call_validator(TRACKBACK_PREFIX, TRACKBACK_URI)
+ %w(ping).each do |name|
+ var_name = "#{TRACKBACK_PREFIX}_#{name}"
+ klass_name = "TrackBack#{Utils.to_class_name(name)}"
+ klass.install_have_child_element(name, TRACKBACK_URI, "?", var_name)
+ klass.module_eval(<<-EOC, __FILE__, __LINE__)
+ remove_method :#{var_name}
+ def #{var_name}
+ @#{var_name} and @#{var_name}.value
+ end
+
+ remove_method :#{var_name}=
+ def #{var_name}=(value)
+ @#{var_name} = Utils.new_with_value_if_need(#{klass_name}, value)
+ end
+ EOC
+ end
+
+ [%w(about s)].each do |name, postfix|
+ var_name = "#{TRACKBACK_PREFIX}_#{name}"
+ klass_name = "TrackBack#{Utils.to_class_name(name)}"
+ klass.install_have_children_element(name, TRACKBACK_URI, "*",
+ var_name)
+ klass.module_eval(<<-EOC, __FILE__, __LINE__)
+ remove_method :#{var_name}
+ def #{var_name}(*args)
+ if args.empty?
+ @#{var_name}.first and @#{var_name}.first.value
+ else
+ ret = @#{var_name}.__send__("[]", *args)
+ if ret.is_a?(Array)
+ ret.collect {|x| x.value}
+ else
+ ret.value
+ end
+ end
+ end
+
+ remove_method :#{var_name}=
+ remove_method :set_#{var_name}
+ def #{var_name}=(*args)
+ if args.size == 1
+ item = Utils.new_with_value_if_need(#{klass_name}, args[0])
+ @#{var_name}.push(item)
+ else
+ new_val = args.last
+ if new_val.is_a?(Array)
+ new_val = new_value.collect do |val|
+ Utils.new_with_value_if_need(#{klass_name}, val)
+ end
+ else
+ new_val = Utils.new_with_value_if_need(#{klass_name}, new_val)
+ end
+ @#{var_name}.__send__("[]=", *(args[0..-2] + [new_val]))
+ end
+ end
+ alias set_#{var_name} #{var_name}=
+ EOC
+ end
+ end
+ end
+ end
+
+ module TrackBackModel10
+ extend BaseModel
+ extend BaseTrackBackModel
+
+ class TrackBackPing < Element
+ include RSS10
+
+ class << self
+
+ def required_prefix
+ TRACKBACK_PREFIX
+ end
+
+ def required_uri
+ TRACKBACK_URI
+ end
+
+ end
+
+ @tag_name = "ping"
+
+ [
+ ["resource", ::RSS::RDF::URI, true]
+ ].each do |name, uri, required|
+ install_get_attribute(name, uri, required, nil, nil,
+ "#{::RSS::RDF::PREFIX}:#{name}")
+ end
+
+ alias_method(:value, :resource)
+ alias_method(:value=, :resource=)
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.resource = args[0]
+ end
+ end
+
+ def full_name
+ tag_name_with_prefix(TRACKBACK_PREFIX)
+ end
+ end
+
+ class TrackBackAbout < Element
+ include RSS10
+
+ class << self
+
+ def required_prefix
+ TRACKBACK_PREFIX
+ end
+
+ def required_uri
+ TRACKBACK_URI
+ end
+
+ end
+
+ @tag_name = "about"
+
+ [
+ ["resource", ::RSS::RDF::URI, true]
+ ].each do |name, uri, required|
+ install_get_attribute(name, uri, required, nil, nil,
+ "#{::RSS::RDF::PREFIX}:#{name}")
+ end
+
+ alias_method(:value, :resource)
+ alias_method(:value=, :resource=)
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.resource = args[0]
+ end
+ end
+
+ def full_name
+ tag_name_with_prefix(TRACKBACK_PREFIX)
+ end
+
+ private
+ def maker_target(abouts)
+ abouts.new_about
+ end
+
+ def setup_maker_attributes(about)
+ about.resource = self.resource
+ end
+
+ end
+ end
+
+ module TrackBackModel20
+ extend BaseModel
+ extend BaseTrackBackModel
+
+ class TrackBackPing < Element
+ include RSS09
+
+ @tag_name = "ping"
+
+ content_setup
+
+ class << self
+
+ def required_prefix
+ TRACKBACK_PREFIX
+ end
+
+ def required_uri
+ TRACKBACK_URI
+ end
+
+ end
+
+ alias_method(:value, :content)
+ alias_method(:value=, :content=)
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.content = args[0]
+ end
+ end
+
+ def full_name
+ tag_name_with_prefix(TRACKBACK_PREFIX)
+ end
+
+ end
+
+ class TrackBackAbout < Element
+ include RSS09
+
+ @tag_name = "about"
+
+ content_setup
+
+ class << self
+
+ def required_prefix
+ TRACKBACK_PREFIX
+ end
+
+ def required_uri
+ TRACKBACK_URI
+ end
+
+ end
+
+ alias_method(:value, :content)
+ alias_method(:value=, :content=)
+
+ def initialize(*args)
+ if Utils.element_initialize_arguments?(args)
+ super
+ else
+ super()
+ self.content = args[0]
+ end
+ end
+
+ def full_name
+ tag_name_with_prefix(TRACKBACK_PREFIX)
+ end
+
+ end
+ end
+
+ class RDF
+ class Item; include TrackBackModel10; end
+ end
+
+ class Rss
+ class Channel
+ class Item; include TrackBackModel20; end
+ end
+ end
+
+ BaseTrackBackModel::ELEMENTS.each do |name|
+ class_name = Utils.to_class_name(name)
+ BaseListener.install_class_name(TRACKBACK_URI, name,
+ "TrackBack#{class_name}")
+ end
+
+ BaseTrackBackModel::ELEMENTS.collect! {|name| "#{TRACKBACK_PREFIX}_#{name}"}
+end
diff --git a/trunk/lib/rss/utils.rb b/trunk/lib/rss/utils.rb
new file mode 100644
index 0000000000..0e4001e1f3
--- /dev/null
+++ b/trunk/lib/rss/utils.rb
@@ -0,0 +1,111 @@
+module RSS
+ module Utils
+ module_function
+
+ # Convert a name_with_underscores to CamelCase.
+ def to_class_name(name)
+ name.split(/[_\-]/).collect do |part|
+ "#{part[0, 1].upcase}#{part[1..-1]}"
+ end.join("")
+ end
+
+ def get_file_and_line_from_caller(i=0)
+ file, line, = caller[i].split(':')
+ line = line.to_i
+ line += 1 if i.zero?
+ [file, line]
+ end
+
+ # escape '&', '"', '<' and '>' for use in HTML.
+ def html_escape(s)
+ s.to_s.gsub(/&/, "&amp;").gsub(/\"/, "&quot;").gsub(/>/, "&gt;").gsub(/</, "&lt;")
+ end
+ alias h html_escape
+
+ # If +value+ is an instance of class +klass+, return it, else
+ # create a new instance of +klass+ with value +value+.
+ def new_with_value_if_need(klass, value)
+ if value.is_a?(klass)
+ value
+ else
+ klass.new(value)
+ end
+ end
+
+ def element_initialize_arguments?(args)
+ [true, false].include?(args[0]) and args[1].is_a?(Hash)
+ end
+
+ module YesCleanOther
+ module_function
+ def parse(value)
+ if [true, false, nil].include?(value)
+ value
+ else
+ case value.to_s
+ when /\Ayes\z/i
+ true
+ when /\Aclean\z/i
+ false
+ else
+ nil
+ end
+ end
+ end
+ end
+
+ module YesOther
+ module_function
+ def parse(value)
+ if [true, false].include?(value)
+ value
+ else
+ /\Ayes\z/i.match(value.to_s) ? true : false
+ end
+ end
+ end
+
+ module CSV
+ module_function
+ def parse(value, &block)
+ if value.is_a?(String)
+ value = value.strip.split(/\s*,\s*/)
+ value = value.collect(&block) if block_given?
+ value
+ else
+ value
+ end
+ end
+ end
+
+ module InheritedReader
+ def inherited_reader(constant_name)
+ base_class = inherited_base
+ result = base_class.const_get(constant_name)
+ found_base_class = false
+ ancestors.reverse_each do |klass|
+ if found_base_class
+ if klass.const_defined?(constant_name)
+ result = yield(result, klass.const_get(constant_name))
+ end
+ else
+ found_base_class = klass == base_class
+ end
+ end
+ result
+ end
+
+ def inherited_array_reader(constant_name)
+ inherited_reader(constant_name) do |result, current|
+ current + result
+ end
+ end
+
+ def inherited_hash_reader(constant_name)
+ inherited_reader(constant_name) do |result, current|
+ result.merge(current)
+ end
+ end
+ end
+ end
+end
diff --git a/trunk/lib/rss/xml-stylesheet.rb b/trunk/lib/rss/xml-stylesheet.rb
new file mode 100644
index 0000000000..559d6bcd56
--- /dev/null
+++ b/trunk/lib/rss/xml-stylesheet.rb
@@ -0,0 +1,105 @@
+require "rss/utils"
+
+module RSS
+
+ module XMLStyleSheetMixin
+ attr_accessor :xml_stylesheets
+ def initialize(*args)
+ super
+ @xml_stylesheets = []
+ end
+
+ private
+ def xml_stylesheet_pi
+ xsss = @xml_stylesheets.collect do |xss|
+ pi = xss.to_s
+ pi = nil if /\A\s*\z/ =~ pi
+ pi
+ end.compact
+ xsss.push("") unless xsss.empty?
+ xsss.join("\n")
+ end
+ end
+
+ class XMLStyleSheet
+
+ include Utils
+
+ ATTRIBUTES = %w(href type title media charset alternate)
+
+ GUESS_TABLE = {
+ "xsl" => "text/xsl",
+ "css" => "text/css",
+ }
+
+ attr_accessor(*ATTRIBUTES)
+ attr_accessor(:do_validate)
+ def initialize(*attrs)
+ if attrs.size == 1 and
+ (attrs.first.is_a?(Hash) or attrs.first.is_a?(Array))
+ attrs = attrs.first
+ end
+ @do_validate = true
+ ATTRIBUTES.each do |attr|
+ __send__("#{attr}=", nil)
+ end
+ vars = ATTRIBUTES.dup
+ vars.unshift(:do_validate)
+ attrs.each do |name, value|
+ if vars.include?(name.to_s)
+ __send__("#{name}=", value)
+ end
+ end
+ end
+
+ def to_s
+ rv = ""
+ if @href
+ rv << %Q[<?xml-stylesheet]
+ ATTRIBUTES.each do |name|
+ if __send__(name)
+ rv << %Q[ #{name}="#{h __send__(name)}"]
+ end
+ end
+ rv << %Q[?>]
+ end
+ rv
+ end
+
+ remove_method(:href=)
+ def href=(value)
+ @href = value
+ if @href and @type.nil?
+ @type = guess_type(@href)
+ end
+ @href
+ end
+
+ remove_method(:alternate=)
+ def alternate=(value)
+ if value.nil? or /\A(?:yes|no)\z/ =~ value
+ @alternate = value
+ else
+ if @do_validate
+ args = ["?xml-stylesheet?", %Q[alternate="#{value}"]]
+ raise NotAvailableValueError.new(*args)
+ end
+ end
+ @alternate
+ end
+
+ def setup_maker(maker)
+ xss = maker.xml_stylesheets.new_xml_stylesheet
+ ATTRIBUTES.each do |attr|
+ xss.__send__("#{attr}=", __send__(attr))
+ end
+ end
+
+ private
+ def guess_type(filename)
+ /\.([^.]+)$/ =~ filename
+ GUESS_TABLE[$1]
+ end
+
+ end
+end
diff --git a/trunk/lib/rss/xml.rb b/trunk/lib/rss/xml.rb
new file mode 100644
index 0000000000..1ae878b772
--- /dev/null
+++ b/trunk/lib/rss/xml.rb
@@ -0,0 +1,71 @@
+require "rss/utils"
+
+module RSS
+ module XML
+ class Element
+ include Enumerable
+
+ attr_reader :name, :prefix, :uri, :attributes, :children
+ def initialize(name, prefix=nil, uri=nil, attributes={}, children=[])
+ @name = name
+ @prefix = prefix
+ @uri = uri
+ @attributes = attributes
+ if children.is_a?(String) or !children.respond_to?(:each)
+ @children = [children]
+ else
+ @children = children
+ end
+ end
+
+ def [](name)
+ @attributes[name]
+ end
+
+ def []=(name, value)
+ @attributes[name] = value
+ end
+
+ def <<(child)
+ @children << child
+ end
+
+ def each(&block)
+ @children.each(&block)
+ end
+
+ def ==(other)
+ other.kind_of?(self.class) and
+ @name == other.name and
+ @uri == other.uri and
+ @attributes == other.attributes and
+ @children == other.children
+ end
+
+ def to_s
+ rv = "<#{full_name}"
+ attributes.each do |key, value|
+ rv << " #{Utils.html_escape(key)}=\"#{Utils.html_escape(value)}\""
+ end
+ if children.empty?
+ rv << "/>"
+ else
+ rv << ">"
+ children.each do |child|
+ rv << child.to_s
+ end
+ rv << "</#{full_name}>"
+ end
+ rv
+ end
+
+ def full_name
+ if @prefix
+ "#{@prefix}:#{@name}"
+ else
+ @name
+ end
+ end
+ end
+ end
+end
diff --git a/trunk/lib/rss/xmlparser.rb b/trunk/lib/rss/xmlparser.rb
new file mode 100644
index 0000000000..3dfe7d461a
--- /dev/null
+++ b/trunk/lib/rss/xmlparser.rb
@@ -0,0 +1,93 @@
+begin
+ require "xml/parser"
+rescue LoadError
+ require "xmlparser"
+end
+
+begin
+ require "xml/encoding-ja"
+rescue LoadError
+ require "xmlencoding-ja"
+ if defined?(Kconv)
+ module XMLEncoding_ja
+ class SJISHandler
+ include Kconv
+ end
+ end
+ end
+end
+
+module XML
+ class Parser
+ unless defined?(Error)
+ Error = ::XMLParserError
+ end
+ end
+end
+
+module RSS
+
+ class REXMLLikeXMLParser < ::XML::Parser
+
+ include ::XML::Encoding_ja
+
+ def listener=(listener)
+ @listener = listener
+ end
+
+ def startElement(name, attrs)
+ @listener.tag_start(name, attrs)
+ end
+
+ def endElement(name)
+ @listener.tag_end(name)
+ end
+
+ def character(data)
+ @listener.text(data)
+ end
+
+ def xmlDecl(version, encoding, standalone)
+ @listener.xmldecl(version, encoding, standalone == 1)
+ end
+
+ def processingInstruction(target, content)
+ @listener.instruction(target, content)
+ end
+
+ end
+
+ class XMLParserParser < BaseParser
+
+ class << self
+ def listener
+ XMLParserListener
+ end
+ end
+
+ private
+ def _parse
+ begin
+ parser = REXMLLikeXMLParser.new
+ parser.listener = @listener
+ parser.parse(@rss)
+ rescue ::XML::Parser::Error => e
+ raise NotWellFormedError.new(parser.line){e.message}
+ end
+ end
+
+ end
+
+ class XMLParserListener < BaseListener
+
+ include ListenerMixin
+
+ def xmldecl(version, encoding, standalone)
+ super
+ # Encoding is converted to UTF-8 when XMLParser parses XML.
+ @encoding = 'UTF-8'
+ end
+
+ end
+
+end
diff --git a/trunk/lib/rss/xmlscanner.rb b/trunk/lib/rss/xmlscanner.rb
new file mode 100644
index 0000000000..61b9fa6bf4
--- /dev/null
+++ b/trunk/lib/rss/xmlscanner.rb
@@ -0,0 +1,121 @@
+require 'xmlscan/scanner'
+require 'stringio'
+
+module RSS
+
+ class XMLScanParser < BaseParser
+
+ class << self
+ def listener
+ XMLScanListener
+ end
+ end
+
+ private
+ def _parse
+ begin
+ if @rss.is_a?(String)
+ input = StringIO.new(@rss)
+ else
+ input = @rss
+ end
+ scanner = XMLScan::XMLScanner.new(@listener)
+ scanner.parse(input)
+ rescue XMLScan::Error => e
+ lineno = e.lineno || scanner.lineno || input.lineno
+ raise NotWellFormedError.new(lineno){e.message}
+ end
+ end
+
+ end
+
+ class XMLScanListener < BaseListener
+
+ include XMLScan::Visitor
+ include ListenerMixin
+
+ ENTITIES = {
+ 'lt' => '<',
+ 'gt' => '>',
+ 'amp' => '&',
+ 'quot' => '"',
+ 'apos' => '\''
+ }
+
+ def on_xmldecl_version(str)
+ @version = str
+ end
+
+ def on_xmldecl_encoding(str)
+ @encoding = str
+ end
+
+ def on_xmldecl_standalone(str)
+ @standalone = str
+ end
+
+ def on_xmldecl_end
+ xmldecl(@version, @encoding, @standalone == "yes")
+ end
+
+ alias_method(:on_pi, :instruction)
+ alias_method(:on_chardata, :text)
+ alias_method(:on_cdata, :text)
+
+ def on_etag(name)
+ tag_end(name)
+ end
+
+ def on_entityref(ref)
+ text(entity(ref))
+ end
+
+ def on_charref(code)
+ text([code].pack('U'))
+ end
+
+ alias_method(:on_charref_hex, :on_charref)
+
+ def on_stag(name)
+ @attrs = {}
+ end
+
+ def on_attribute(name)
+ @attrs[name] = @current_attr = ''
+ end
+
+ def on_attr_value(str)
+ @current_attr << str
+ end
+
+ def on_attr_entityref(ref)
+ @current_attr << entity(ref)
+ end
+
+ def on_attr_charref(code)
+ @current_attr << [code].pack('U')
+ end
+
+ alias_method(:on_attr_charref_hex, :on_attr_charref)
+
+ def on_stag_end(name)
+ tag_start(name, @attrs)
+ end
+
+ def on_stag_end_empty(name)
+ tag_start(name, @attrs)
+ tag_end(name)
+ end
+
+ private
+ def entity(ref)
+ ent = ENTITIES[ref]
+ if ent
+ ent
+ else
+ wellformed_error("undefined entity: #{ref}")
+ end
+ end
+ end
+
+end