diff options
Diffstat (limited to 'lib')
31 files changed, 4032 insertions, 1968 deletions
diff --git a/lib/rss.rb b/lib/rss.rb index 495edb1b98..bbe19ad95c 100644 --- a/lib/rss.rb +++ b/lib/rss.rb @@ -1,4 +1,4 @@ -# Copyright (c) 2003-2005 Kouhei Sutou. You can redistribute it and/or +# Copyright (c) 2003-2006 Kouhei Sutou. You can redistribute it and/or # modify it under the same terms as Ruby. # # Author:: Kouhei Sutou <kou@cozmixng.org> @@ -6,11 +6,12 @@ require 'rss/1.0' require 'rss/2.0' +require 'rss/atom' require 'rss/content' require 'rss/dublincore' require 'rss/image' require 'rss/syndication' -#require 'rss/taxonomy' +require 'rss/taxonomy' require 'rss/trackback' require "rss/maker" diff --git a/lib/rss/0.9.rb b/lib/rss/0.9.rb index 900536869d..7b24e7596d 100644 --- a/lib/rss/0.9.rb +++ b/lib/rss/0.9.rb @@ -9,7 +9,7 @@ module RSS def self.append_features(klass) super - klass.install_must_call_validator('', nil) + klass.install_must_call_validator('', "") end end @@ -17,22 +17,18 @@ module RSS include RSS09 include RootElementMixin - # include XMLStyleSheetMixin - - [ - ["channel", nil], - ].each do |tag, occurs| - install_model(tag, occurs) - end %w(channel).each do |name| - install_have_child_element(name) + install_have_child_element(name, "", nil) end - attr_accessor :rss_version, :version, :encoding, :standalone - - def initialize(rss_version, version=nil, encoding=nil, standalone=nil) + 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 @@ -58,34 +54,20 @@ module RSS nil end end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent, ns_declarations) do |next_indent| - [ - channel_element(false, next_indent), - other_element(false, next_indent), - ] - end - rv = convert(rv) if need_convert - rv - end - private - def children - [@channel] - end - - def _tags - [ - [nil, 'channel'], - ].delete_if do |uri, name| - __send__(name).nil? + 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, "rss_version"], + ["version", true, "feed_version"], ] end @@ -94,119 +76,30 @@ module RSS include RSS09 [ - ["title", nil], - ["link", nil], - ["description", nil], - ["language", nil], - ["copyright", "?"], - ["managingEditor", "?"], - ["webMaster", "?"], - ["rating", "?"], - ["docs", "?"], - ].each do |name, occurs| - install_text_element(name) - install_model(name, occurs) - end - - [ - ["pubDate", "?"], - ["lastBuildDate", "?"], - ].each do |name, occurs| - install_date_element(name, 'rfc822') - install_model(name, occurs) + ["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= - [ - ["skipDays", "?"], - ["skipHours", "?"], - ["image", nil], - ["textInput", "?"], - ].each do |name, occurs| - install_have_child_element(name) - install_model(name, occurs) - end - - [ - ["cloud", "?"] - ].each do |name, occurs| - install_have_attribute_element(name) - install_model(name, occurs) - end - - [ - ["item", "*"] - ].each do |name, occurs| - install_have_children_element(name) - install_model(name, occurs) - end - - def initialize() - super() - end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - title_element(false, next_indent), - link_element(false, next_indent), - description_element(false, next_indent), - language_element(false, next_indent), - copyright_element(false, next_indent), - managingEditor_element(false, next_indent), - webMaster_element(false, next_indent), - rating_element(false, next_indent), - pubDate_element(false, next_indent), - lastBuildDate_element(false, next_indent), - docs_element(false, next_indent), - cloud_element(false, next_indent), - skipDays_element(false, next_indent), - skipHours_element(false, next_indent), - image_element(false, next_indent), - item_elements(false, next_indent), - textInput_element(false, next_indent), - other_element(false, next_indent), - ] - end - rv = convert(rv) if need_convert - rv - end - private - def children - [@skipDays, @skipHours, @image, @textInput, @cloud, *@item] - end - - def _tags - rv = [ - "title", - "link", - "description", - "language", - "copyright", - "managingEditor", - "webMaster", - "rating", - "docs", - "skipDays", - "skipHours", - "image", - "textInput", - "cloud", - ].delete_if do |name| - __send__(name).nil? - end.collect do |elem| - [nil, elem] - end - - @item.each do - rv << [nil, "item"] - end - - rv - end - def maker_target(maker) maker.channel end @@ -237,29 +130,7 @@ module RSS [ ["day", "*"] ].each do |name, occurs| - install_have_children_element(name) - install_model(name, occurs) - end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - day_elements(false, next_indent) - ] - end - rv = convert(rv) if need_convert - rv - end - - private - def children - @day - end - - def _tags - @day.compact.collect do - [nil, "day"] - end + install_have_children_element(name, "", occurs) end class Day < Element @@ -267,9 +138,13 @@ module RSS content_setup - def initialize(content=nil) - super() - self.content = content + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.content = args[0] + end end end @@ -282,29 +157,7 @@ module RSS [ ["hour", "*"] ].each do |name, occurs| - install_have_children_element(name) - install_model(name, occurs) - end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - hour_elements(false, next_indent) - ] - end - rv = convert(rv) if need_convert - rv - end - - private - def children - @hour - end - - def _tags - @hour.compact.collect do - [nil, "hour"] - end + install_have_children_element(name, "", occurs) end class Hour < Element @@ -312,9 +165,13 @@ module RSS content_setup(:integer) - def initialize(content=nil) - super() - self.content = content + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.content = args[0] + end end end @@ -325,54 +182,31 @@ module RSS include RSS09 %w(url title link).each do |name| - install_text_element(name) - install_model(name, nil) + install_text_element(name, "", nil) end [ ["width", :integer], ["height", :integer], ["description"], ].each do |name, type| - install_text_element(name, type) - install_model(name, "?") - end - - def initialize(url=nil, title=nil, link=nil, width=nil, height=nil, - description=nil) - super() - self.url = url - self.title = title - self.link = link - self.width = width - self.height = height - self.description = description + install_text_element(name, "", "?", name, type) end - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - url_element(false, next_indent), - title_element(false, next_indent), - link_element(false, next_indent), - width_element(false, next_indent), - height_element(false, next_indent), - description_element(false, next_indent), - other_element(false, next_indent), - ] + 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 - rv = convert(rv) if need_convert - rv end private - def _tags - %w(url title link width height description).delete_if do |name| - __send__(name).nil? - end.collect do |elem| - [nil, elem] - end - end - def maker_target(maker) maker.image end @@ -381,106 +215,47 @@ module RSS class Cloud < Element include RSS09 - + [ - ["domain", nil, true], - ["port", nil, true, :integer], - ["path", nil, true], - ["registerProcedure", nil, true], - ["protocol", nil, true], + ["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(domain=nil, port=nil, path=nil, rp=nil, protocol=nil) - super() - self.domain = domain - self.port = port - self.path = path - self.registerProcedure = rp - self.protocol = protocol - end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) - rv = convert(rv) if need_convert - rv - end - - private - def _attrs - %w(domain port path registerProcedure protocol).collect do |attr| - [attr, true] + 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 - %w(title link description).each do |name| - install_text_element(name) - end - - %w(source enclosure).each do |name| - install_have_child_element(name) - end - [ - %w(category categories), - ].each do |name, plural_name| - install_have_children_element(name, plural_name) - end - - [ - ["title", '?'], - ["link", '?'], - ["description", '?'], - ["category", '*'], - ["source", '?'], - ["enclosure", '?'], - ].each do |tag, occurs| - install_model(tag, occurs) - end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - title_element(false, next_indent), - link_element(false, next_indent), - description_element(false, next_indent), - category_elements(false, next_indent), - source_element(false, next_indent), - enclosure_element(false, next_indent), - other_element(false, next_indent), - ] - end - rv = convert(rv) if need_convert - rv + ["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 children - [@source, @enclosure, *@category].compact - end - - def _tags - rv = %w(title link description author comments - source enclosure).delete_if do |name| - __send__(name).nil? - end.collect do |name| - [nil, name] - end - - @category.each do - rv << [nil, "category"] - end - - rv - end - def maker_target(items) if items.respond_to?("items") # For backward compatibility @@ -500,31 +275,24 @@ module RSS include RSS09 [ - ["url", nil, true] + ["url", "", true] ].each do |name, uri, required| install_get_attribute(name, uri, required) end content_setup - def initialize(url=nil, content=nil) - super() - self.url = url - self.content = content + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.url = args[0] + self.content = args[1] + end end private - def _tags - [] - end - - def _attrs - [ - ["url", true] - ] - end - - def maker_target(item) item.source end @@ -540,35 +308,25 @@ module RSS include RSS09 [ - ["url", nil, true], - ["length", nil, true, :integer], - ["type", nil, true], + ["url", "", true], + ["length", "", true, :integer], + ["type", "", true], ].each do |name, uri, required, type| install_get_attribute(name, uri, required, type) end - def initialize(url=nil, length=nil, type=nil) - super() - self.url = url - self.length = length - self.type = type - end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) - rv = convert(rv) if need_convert - rv + 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 _attrs - [ - ["url", true], - ["length", true], - ["type", true], - ] - end - def maker_target(item) item.enclosure end @@ -585,26 +343,24 @@ module RSS include RSS09 [ - ["domain", nil, false] + ["domain", "", false] ].each do |name, uri, required| install_get_attribute(name, uri, required) end content_setup - def initialize(domain=nil, content=nil) - super() - self.domain = domain - self.content = content + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.domain = args[0] + self.content = args[1] + end end private - def _attrs - [ - ["domain", false] - ] - end - def maker_target(item) item.new_category end @@ -623,41 +379,22 @@ module RSS include RSS09 %w(title description name link).each do |name| - install_text_element(name) - install_model(name, nil) + install_text_element(name, "", nil) end - def initialize(title=nil, description=nil, name=nil, link=nil) - super() - self.title = title - self.description = description - self.name = name - self.link = link - end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - title_element(false, next_indent), - description_element(false, next_indent), - name_element(false, next_indent), - link_element(false, next_indent), - other_element(false, next_indent), - ] + 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 - rv = convert(rv) if need_convert - rv end private - def _tags - %w(title description name link).each do |name| - __send__(name).nil? - end.collect do |elem| - [nil, elem] - end - end - def maker_target(maker) maker.textinput end @@ -668,21 +405,22 @@ module RSS end RSS09::ELEMENTS.each do |name| - BaseListener.install_get_text_element(nil, name, "#{name}=") + BaseListener.install_get_text_element("", name, name) end module ListenerMixin private - def start_rss(tag_name, prefix, attrs, ns) - check_ns(tag_name, prefix, ns, nil) + 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 - @proc_stack.push Proc.new { |text, tags| - @rss.validate_for_stream(tags) if @do_validate - } + pr = Proc.new do |text, tags| + @rss.validate_for_stream(tags, @ignore_unknown_element) if @do_validate + end + @proc_stack.push(pr) end end diff --git a/lib/rss/1.0.rb b/lib/rss/1.0.rb index 36d6f5df87..f04e61c5eb 100644 --- a/lib/rss/1.0.rb +++ b/lib/rss/1.0.rb @@ -38,62 +38,22 @@ module RSS [ ["channel", nil], ["image", "?"], - ["item", "+"], + ["item", "+", :children], ["textinput", "?"], - ].each do |tag, occurs| - install_model(tag, occurs) + ].each do |tag, occurs, type| + type ||= :child + __send__("install_have_#{type}_element", tag, ::RSS::URI, occurs) end - %w(channel image textinput).each do |name| - install_have_child_element(name) - end - - install_have_children_element("item") - - attr_accessor :rss_version, :version, :encoding, :standalone - + 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 - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent, ns_declarations) do |next_indent| - [ - channel_element(false, next_indent), - image_element(false, next_indent), - item_elements(false, next_indent), - textinput_element(false, next_indent), - other_element(false, next_indent), - ] - end - rv = convert(rv) if need_convert - rv - end - - private - def rdf_validate(tags) - _validate(tags, []) - end - - def children - [@channel, @image, @textinput, *@item] - end - - def _tags - rv = [ - [::RSS::URI, "channel"], - [::RSS::URI, "image"], - ].delete_if {|uri, name| __send__(name).nil?} - @item.each do |item| - rv << [::RSS::URI, "item"] - end - rv << [::RSS::URI, "textinput"] if @textinput - rv - end class Li < Element @@ -106,32 +66,23 @@ module RSS end [ - ["resource", [URI, nil], true] + ["resource", [URI, ""], true] ].each do |name, uri, required| install_get_attribute(name, uri, required) end - def initialize(resource=nil) - super() - self.resource = 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(PREFIX) end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) - rv = convert(rv) if need_convert - rv - end - - private - def _attrs - [ - ["resource", true] - ] - end end class Seq < Element @@ -148,21 +99,15 @@ module RSS @tag_name = 'Seq' - install_have_children_element("li") - + install_have_children_element("li", URI, "*") install_must_call_validator('rdf', ::RSS::RDF::URI) - def initialize(li=[]) - super() - @li = li - end - - def to_s(need_convert=true, indent=calc_indent) - tag(indent) do |next_indent| - [ - li_elements(need_convert, next_indent), - other_element(need_convert, next_indent), - ] + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + @li = args[0] if args[0] end end @@ -175,23 +120,6 @@ module RSS target << li.resource end end - - private - def children - @li - end - - def rdf_validate(tags) - _validate(tags, [["li", '*']]) - end - - def _tags - rv = [] - @li.each do |li| - rv << [URI, "li"] - end - rv - end end class Bag < Element @@ -208,21 +136,15 @@ module RSS @tag_name = 'Bag' - install_have_children_element("li") - - install_must_call_validator('rdf', ::RSS::RDF::URI) - - def initialize(li=[]) - super() - @li = li - end + install_have_children_element("li", URI, "*") + install_must_call_validator('rdf', URI) - def to_s(need_convert=true, indent=calc_indent) - tag(indent) do |next_indent| - [ - li_elements(need_convert, next_indent), - other_element(need_convert, next_indent), - ] + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + @li = args[0] if args[0] end end @@ -235,23 +157,6 @@ module RSS target << li.resource end end - - private - def children - @li - end - - def rdf_validate(tags) - _validate(tags, [["li", '*']]) - end - - def _tags - rv = [] - @li.each do |li| - rv << [URI, "li"] - end - rv - end end class Channel < Element @@ -269,73 +174,31 @@ module RSS [ ["about", URI, true] ].each do |name, uri, required| - install_get_attribute(name, uri, required) - end - - %w(title link description).each do |name| - install_text_element(name) + install_get_attribute(name, uri, required, nil, nil, + "#{PREFIX}:#{name}") end - %w(image items textinput).each do |name| - install_have_child_element(name) - end - [ - ['title', nil], - ['link', nil], - ['description', nil], - ['image', '?'], - ['items', nil], - ['textinput', '?'], - ].each do |tag, occurs| - install_model(tag, occurs) - end - - def initialize(about=nil) - super() - self.about = about - end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - title_element(false, next_indent), - link_element(false, next_indent), - description_element(false, next_indent), - image_element(false, next_indent), - items_element(false, next_indent), - textinput_element(false, next_indent), - other_element(false, next_indent), - ] + ['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 - rv = convert(rv) if need_convert - rv end private - def children - [@image, @items, @textinput] - end - - def _tags - [ - [::RSS::URI, 'title'], - [::RSS::URI, 'link'], - [::RSS::URI, 'description'], - [::RSS::URI, 'image'], - [::RSS::URI, 'items'], - [::RSS::URI, 'textinput'], - ].delete_if do |uri, name| - __send__(name).nil? - end - end - - def _attrs - [ - ["#{PREFIX}:about", true, "about"] - ] - end - def maker_target(maker) maker.channel end @@ -359,25 +222,17 @@ module RSS [ ["resource", URI, true] ].each do |name, uri, required| - install_get_attribute(name, uri, required) + install_get_attribute(name, uri, required, nil, nil, + "#{PREFIX}:#{name}") end - def initialize(resource=nil) - super() - self.resource = resource - end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) - rv = convert(rv) if need_convert - rv - end - - private - def _attrs - [ - ["#{PREFIX}:resource", true, "resource"] - ] + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.resource = args[0] + end end end @@ -396,25 +251,17 @@ module RSS [ ["resource", URI, true] ].each do |name, uri, required| - install_get_attribute(name, uri, required) + install_get_attribute(name, uri, required, nil, nil, + "#{PREFIX}:#{name}") end - def initialize(resource=nil) - super() - self.resource = resource - end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) - rv = convert(rv) if need_convert - rv - end - - private - def _attrs - [ - ["#{PREFIX}:resource", true, "resource"] - ] + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.resource = args[0] + end end end @@ -432,22 +279,17 @@ module RSS end - install_have_child_element("Seq") - - install_must_call_validator('rdf', ::RSS::RDF::URI) + install_have_child_element("Seq", URI, nil) + install_must_call_validator('rdf', URI) - def initialize(seq=Seq.new) - super() - @Seq = seq - end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - Seq_element(need_convert, next_indent), - other_element(need_convert, next_indent), - ] + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.Seq = args[0] end + self.Seq ||= Seq.new end def resources @@ -459,21 +301,6 @@ module RSS [] end end - - private - def children - [@Seq] - end - - def _tags - rv = [] - rv << [URI, 'Seq'] unless @Seq.nil? - rv - end - - def rdf_validate(tags) - _validate(tags, [["Seq", nil]]) - end end end @@ -488,60 +315,28 @@ module RSS end end - + [ ["about", URI, true] ].each do |name, uri, required| - install_get_attribute(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) - end - - [ - ['title', nil], - ['url', nil], - ['link', nil], - ].each do |tag, occurs| - install_model(tag, occurs) + install_text_element(name, ::RSS::URI, nil) end - def initialize(about=nil) - super() - self.about = about - end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - title_element(false, next_indent), - url_element(false, next_indent), - link_element(false, next_indent), - other_element(false, next_indent), - ] + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.about = args[0] end - rv = convert(rv) if need_convert - rv end private - def _tags - [ - [::RSS::URI, 'title'], - [::RSS::URI, 'url'], - [::RSS::URI, 'link'], - ].delete_if do |uri, name| - __send__(name).nil? - end - end - - def _attrs - [ - ["#{PREFIX}:about", true, "about"] - ] - end - def maker_target(maker) maker.image end @@ -559,14 +354,12 @@ module RSS end + [ ["about", URI, true] ].each do |name, uri, required| - install_get_attribute(name, uri, required) - end - - %w(title link description).each do |name| - install_text_element(name) + install_get_attribute(name, uri, required, nil, nil, + "#{PREFIX}:#{name}") end [ @@ -574,44 +367,19 @@ module RSS ["link", nil], ["description", "?"], ].each do |tag, occurs| - install_model(tag, occurs) + install_text_element(tag, ::RSS::URI, occurs) end - def initialize(about=nil) - super() - self.about = about - end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - title_element(false, next_indent), - link_element(false, next_indent), - description_element(false, next_indent), - other_element(false, next_indent), - ] - end - rv = convert(rv) if need_convert - rv - end - - private - def _tags - [ - [::RSS::URI, 'title'], - [::RSS::URI, 'link'], - [::RSS::URI, 'description'], - ].delete_if do |uri, name| - __send__(name).nil? + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.about = args[0] end end - def _attrs - [ - ["#{PREFIX}:about", true, "about"] - ] - end - + private def maker_target(items) if items.respond_to?("items") # For backward compatibility @@ -636,59 +404,24 @@ module RSS [ ["about", URI, true] ].each do |name, uri, required| - install_get_attribute(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) - end - - [ - ["title", nil], - ["description", nil], - ["name", nil], - ["link", nil], - ].each do |tag, occurs| - install_model(tag, occurs) - end - - def initialize(about=nil) - super() - self.about = about + install_text_element(name, ::RSS::URI, nil) end - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - title_element(false, next_indent), - description_element(false, next_indent), - name_element(false, next_indent), - link_element(false, next_indent), - other_element(false, next_indent), - ] + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.about = args[0] end - rv = convert(rv) if need_convert - rv end private - def _tags - [ - [::RSS::URI, 'title'], - [::RSS::URI, 'description'], - [::RSS::URI, 'name'], - [::RSS::URI, 'link'], - ].delete_if do |uri, name| - __send__(name).nil? - end - end - - def _attrs - [ - ["#{PREFIX}:about", true, "about"] - ] - end - def maker_target(maker) maker.textinput end @@ -697,21 +430,22 @@ module RSS end RSS10::ELEMENTS.each do |name| - BaseListener.install_get_text_element(URI, name, "#{name}=") + BaseListener.install_get_text_element(URI, name, name) end module ListenerMixin private - def start_RDF(tag_name, prefix, attrs, ns) + 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 - @proc_stack.push Proc.new { |text, tags| - @rss.validate_for_stream(tags) if @do_validate - } + pr = Proc.new do |text, tags| + @rss.validate_for_stream(tags, @ignore_unknown_element) if @do_validate + end + @proc_stack.push(pr) end end diff --git a/lib/rss/2.0.rb b/lib/rss/2.0.rb index 1c3c22ee70..3798da4eb7 100644 --- a/lib/rss/2.0.rb +++ b/lib/rss/2.0.rb @@ -10,52 +10,20 @@ module RSS ["generator"], ["ttl", :integer], ].each do |name, type| - install_text_element(name, type) - install_model(name, '?') + install_text_element(name, "", "?", name, type) end [ %w(category categories), ].each do |name, plural_name| - install_have_children_element(name, plural_name) - install_model(name, '*') + install_have_children_element(name, "", "*", name, plural_name) end - + [ ["image", "?"], ["language", "?"], ].each do |name, occurs| - install_model(name, occurs) - end - - def other_element(need_convert, indent) - rv = <<-EOT -#{category_elements(need_convert, indent)} -#{generator_element(need_convert, indent)} -#{ttl_element(need_convert, indent)} -EOT - rv << super - end - - private - alias children09 children - def children - children09 + @category.compact - end - - alias _tags09 _tags - def _tags - rv = %w(generator ttl).delete_if do |name| - __send__(name).nil? - end.collect do |elem| - [nil, elem] - end + _tags09 - - @category.each do - rv << [nil, "category"] - end - - rv + install_model(name, "", occurs) end Category = Item::Category @@ -66,15 +34,13 @@ EOT ["comments", "?"], ["author", "?"], ].each do |name, occurs| - install_text_element(name) - install_model(name, occurs) + install_text_element(name, "", occurs) end [ ["pubDate", '?'], ].each do |name, occurs| - install_date_element(name, 'rfc822') - install_model(name, occurs) + install_date_element(name, "", occurs, name, 'rfc822') end alias date pubDate alias date= pubDate= @@ -82,37 +48,10 @@ EOT [ ["guid", '?'], ].each do |name, occurs| - install_have_child_element(name) - install_model(name, occurs) - end - - def other_element(need_convert, indent) - rv = [ - super, - *%w(author comments pubDate guid).collect do |name| - __send__("#{name}_element", false, indent) - end - ].reject do |value| - /\A\s*\z/.match(value) - end - rv.join("\n") + install_have_child_element(name, "", occurs) end private - alias children09 children - def children - children09 + [@guid].compact - end - - alias _tags09 _tags - def _tags - %w(comments author pubDate guid).delete_if do |name| - __send__(name).nil? - end.collect do |elem| - [nil, elem] - end + _tags09 - end - alias _setup_maker_element setup_maker_element def setup_maker_element(item) _setup_maker_element(item) @@ -124,17 +63,21 @@ EOT include RSS09 [ - ["isPermaLink", nil, false, :boolean] + ["isPermaLink", "", false, :boolean] ].each do |name, uri, required, type| install_get_attribute(name, uri, required, type) end content_setup - def initialize(isPermaLink=nil, content=nil) - super() - self.isPermaLink = isPermaLink - self.content = content + 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? @@ -145,12 +88,6 @@ EOT end private - def _attrs - [ - ["isPermaLink", false] - ] - end - def maker_target(item) item.guid end @@ -168,7 +105,7 @@ EOT end RSS09::ELEMENTS.each do |name| - BaseListener.install_get_text_element(nil, name, "#{name}=") + BaseListener.install_get_text_element("", name, name) end end diff --git a/lib/rss/atom.rb b/lib/rss/atom.rb new file mode 100644 index 0000000000..901e69a4b0 --- /dev/null +++ b/lib/rss/atom.rb @@ -0,0 +1,749 @@ +require 'base64' +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) + 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 + + 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 have_author? + authors.any? {|author| !author.to_s.empty?} or + entries.any? {|entry| entry.__send!(:have_author?, false)} + 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) + generator = target.generator + generator.uri = uri if uri + generator.version = version if version + 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 + + 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 have_author?(check_parent=true) + authors.any? {|author| !author.to_s.empty?} or + (check_parent and @parent and @parent.__send!(:have_author?)) or + (source and source.__send!(: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 + + private + 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 + + 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 have_author? + authors.any? {|author| !author.to_s.empty?} or + (source and source.__send!(: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/lib/rss/content.rb b/lib/rss/content.rb index a732cec973..bb61d9ebc5 100644 --- a/lib/rss/content.rb +++ b/lib/rss/content.rb @@ -15,28 +15,13 @@ module RSS def self.append_features(klass) super - - klass.module_eval(<<-EOC, *get_file_and_line_from_caller(1)) - %w(encoded).each do |name| - install_text_element("\#{CONTENT_PREFIX}_\#{name}") - end - EOC - end - - def content_validate(tags) - counter = {} - ELEMENTS.each do |name| - counter[name] = 0 - end - tags.each do |tag| - key = "#{CONTENT_PREFIX}_#{tag}" - raise UnknownTagError.new(tag, CONTENT_URI) unless counter.has_key?(key) - counter[key] += 1 - raise TooMuchTagError.new(tag, tag_name) if counter[key] > 1 + klass.install_must_call_validator(CONTENT_PREFIX, CONTENT_URI) + %w(encoded).each do |name| + klass.install_text_element(name, CONTENT_URI, "?", + "#{CONTENT_PREFIX}_#{name}") end end - end class RDF @@ -47,7 +32,7 @@ module RSS ContentModel::ELEMENTS.uniq! ContentModel::ELEMENTS.each do |full_name| name = full_name[prefix_size..-1] - BaseListener.install_get_text_element(CONTENT_URI, name, "#{full_name}=") + BaseListener.install_get_text_element(CONTENT_URI, name, full_name) end end diff --git a/lib/rss/converter.rb b/lib/rss/converter.rb index 7ad79db318..415a319188 100644 --- a/lib/rss/converter.rb +++ b/lib/rss/converter.rb @@ -66,7 +66,7 @@ module RSS end end - def def_uconv_convert_if_can(meth, to_enc, from_enc) + def def_uconv_convert_if_can(meth, to_enc, from_enc, nkf_arg) begin require "uconv" def_convert(1) do |value| @@ -79,24 +79,31 @@ module RSS EOC end rescue LoadError - def_iconv_convert(to_enc, from_enc, 1) + 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') + 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') + 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') + 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') + def_uconv_convert_if_can('sjistou8', 'UTF-8', 'Shift_JIS', '-Sw') end def def_to_euc_jp_from_shift_jis diff --git a/lib/rss/dublincore.rb b/lib/rss/dublincore.rb index 79d2ca561c..7ba239f8f1 100644 --- a/lib/rss/dublincore.rb +++ b/lib/rss/dublincore.rb @@ -1,11 +1,8 @@ -require "rss/1.0" +require "rss/rss" module RSS - DC_PREFIX = 'dc' DC_URI = "http://purl.org/dc/elements/1.1/" - - RDF.install_ns(DC_PREFIX, DC_URI) module BaseDublinCoreModel def append_features(klass) @@ -17,10 +14,10 @@ module RSS 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)) - install_have_children_element(#{full_name.dump}, - #{full_plural_name.dump}) - remove_method :#{full_name} remove_method :#{full_name}= remove_method :set_#{full_name} @@ -36,8 +33,17 @@ module RSS EOC end klass.module_eval(<<-EOC, *get_file_and_line_from_caller(0)) - alias date #{DC_PREFIX}_date - alias date= #{DC_PREFIX}_date= + 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 @@ -72,7 +78,7 @@ module RSS } ELEMENT_NAME_INFOS = DublinCoreModel::TEXT_ELEMENTS.to_a - DublinCoreModel::DATE_ELEMENTS.each do |name, _| + DublinCoreModel::DATE_ELEMENTS.each do |name, | ELEMENT_NAME_INFOS << [name, nil] end @@ -100,9 +106,13 @@ module RSS alias_method(:value, :content) alias_method(:value=, :content=) - def initialize(content=nil) - super() - self.content = content + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.content = args[0] + end end def full_name @@ -121,39 +131,23 @@ module RSS 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}, #{name.dump}) - + date_writer("content", #{type.dump}, #{tag_name.dump}) + alias_method(:value=, :content=) end EOC end - - def dc_validate(tags) - tags.each do |tag| - key = "#{DC_PREFIX}_#{tag}" - unless DublinCoreModel::ELEMENTS.include?(key) - raise UnknownTagError.new(tag, DC_URI) - end - end - end - end # For backward compatibility DublincoreModel = DublinCoreModel - class RDF - class Channel; include DublinCoreModel; end - class Image; include DublinCoreModel; end - class Item; include DublinCoreModel; end - class Textinput; include DublinCoreModel; end - end - DublinCoreModel::ELEMENTS.each do |name| class_name = Utils.to_class_name(name) BaseListener.install_class_name(DC_URI, name, "DublinCore#{class_name}") @@ -161,3 +155,7 @@ module RSS 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/lib/rss/dublincore/1.0.rb b/lib/rss/dublincore/1.0.rb new file mode 100644 index 0000000000..e193c6d2c2 --- /dev/null +++ b/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/lib/rss/dublincore/2.0.rb b/lib/rss/dublincore/2.0.rb new file mode 100644 index 0000000000..82ed1888c5 --- /dev/null +++ b/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/lib/rss/dublincore/atom.rb b/lib/rss/dublincore/atom.rb new file mode 100644 index 0000000000..e78df4821b --- /dev/null +++ b/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/lib/rss/image.rb b/lib/rss/image.rb index 9d3326efca..44ee3dd41b 100644 --- a/lib/rss/image.rb +++ b/lib/rss/image.rb @@ -17,9 +17,11 @@ module RSS end module ImageModelUtils - def validate_one_tag_name(name, tags) - invalid = tags.find {|tag| tag != name} - raise UnknownTagError.new(invalid, IMAGE_URI) if invalid + 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 @@ -31,13 +33,11 @@ module RSS def self.append_features(klass) super - klass.install_have_child_element("#{IMAGE_PREFIX}_item") + klass.install_have_child_element("item", IMAGE_URI, "?", + "#{IMAGE_PREFIX}_item") + klass.install_must_call_validator(IMAGE_PREFIX, IMAGE_URI) end - def image_validate(tags) - validate_one_tag_name("item", tags) - end - class ImageItem < Element include RSS10 include DublinCoreModel @@ -53,19 +53,23 @@ module RSS 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) + 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(full_name, :integer, disp_name) - BaseListener.install_get_text_element(IMAGE_URI, tag, "#{full_name}=") + 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= @@ -73,43 +77,21 @@ module RSS alias height= image_height= alias height image_height - def initialize(about=nil, resource=nil) - super() - self.about = about - self.resource = resource + 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 - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - other_element(false, next_indent), - ] - end - rv = convert(rv) if need_convert - rv - end private - def _tags - [ - [IMAGE_URI, 'width'], - [IMAGE_URI, 'height'], - ].delete_if do |uri, name| - __send__(name).nil? - end - end - - def _attrs - [ - ["#{::RSS::RDF::PREFIX}:about", true, "about"], - ["#{::RSS::RDF::PREFIX}:resource", false, "resource"], - ] - end - def maker_target(target) target.image_item end @@ -129,14 +111,12 @@ module RSS super unless klass.class == Module - klass.install_have_child_element("#{IMAGE_PREFIX}_favicon") + klass.install_have_child_element("favicon", IMAGE_URI, "?", + "#{IMAGE_PREFIX}_favicon") + klass.install_must_call_validator(IMAGE_PREFIX, IMAGE_URI) end end - def image_validate(tags) - validate_one_tag_name("favicon", tags) - end - class ImageFavicon < Element include RSS10 include DublinCoreModel @@ -152,12 +132,13 @@ module RSS IMAGE_URI end end - + [ - ["about", ::RSS::RDF::URI, true], - ["size", IMAGE_URI, true], - ].each do |name, uri, required| - install_get_attribute(name, uri, required) + ["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) @@ -171,40 +152,27 @@ module RSS raise NotAvailableValueError.new(full_name, new_value, attr_name) end end - funcall(:_size=, new_value) + __send!(:_size=, new_value) end alias image_size= size= alias image_size size - def initialize(about=nil, size=nil) - super() - self.about = about - self.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 - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - other_element(false, next_indent), - ] - end - rv = convert(rv) if need_convert - rv - end private - def _attrs - [ - ["#{::RSS::RDF::PREFIX}:about", true, "about"], - ["#{IMAGE_PREFIX}:size", true, "size"], - ] - end - def maker_target(target) target.image_favicon end diff --git a/lib/rss/maker.rb b/lib/rss/maker.rb index 9ed799ac7f..a47b55b670 100644 --- a/lib/rss/maker.rb +++ b/lib/rss/maker.rb @@ -1,14 +1,14 @@ require "rss/rss" module RSS - module Maker - MAKERS = {} - + class << self def make(version, &block) - maker(version).make(&block) + m = maker(version) + raise UnsupportedMakerVersionError.new(version) if m.nil? + m.make(&block) end def maker(version) @@ -19,16 +19,21 @@ module RSS MAKERS[version] = maker end - def filename_to_version(filename) - File.basename(filename, ".*") + def versions + MAKERS.keys.uniq.sort + end + + def makers + MAKERS.values.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/syndication" diff --git a/lib/rss/maker/0.9.rb b/lib/rss/maker/0.9.rb index b82585fb96..dd75c9289b 100644 --- a/lib/rss/maker/0.9.rb +++ b/lib/rss/maker/0.9.rb @@ -7,13 +7,14 @@ module RSS class RSS09 < RSSBase - def initialize(rss_version="0.91") + def initialize(feed_version="0.91") super + @feed_type = "rss" end private - def make_rss - Rss.new(@rss_version, @version, @encoding, @standalone) + def make_feed + Rss.new(@feed_version, @version, @encoding, @standalone) end def setup_elements(rss) @@ -22,40 +23,34 @@ module RSS class Channel < ChannelBase - def to_rss(rss) + def to_feed(rss) channel = Rss::Channel.new set = setup_values(channel) - if set + _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) - if rss.channel.image - rss - else - nil - end - elsif variable_is_set? - raise NotSetError.new("maker.channel", not_set_required_variables) + setup_other_elements(rss, channel) + rss + else + raise NotSetError.new("maker.channel", _not_set_required_variables) end end - def have_required_values? - @title and @link and @description and @language - end - private def setup_items(rss) - @maker.items.to_rss(rss) + @maker.items.to_feed(rss) end def setup_image(rss) - @maker.image.to_rss(rss) + @maker.image.to_feed(rss) end def setup_textinput(rss) - @maker.textinput.to_rss(rss) + @maker.textinput.to_feed(rss) end def variables @@ -63,162 +58,409 @@ module RSS end def required_variable_names - %w(title link description language) + %w(link language) end - + + def not_set_required_variables + vars = super + vars << "description" unless description.have_required_values? + vars << "title" unless title.have_required_values? + vars + end + class SkipDays < SkipDaysBase - def to_rss(rss, channel) + 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_rss(rss, skipDays.days) + day.to_feed(rss, skipDays.days) end end end class Day < DayBase - def to_rss(rss, days) + def to_feed(rss, days) day = Rss::Channel::SkipDays::Day.new set = setup_values(day) if set days << day - setup_other_elements(rss) + set_parent(day, days) + setup_other_elements(rss, day) end end - def have_required_values? - @content + private + def required_variable_names + %w(content) end end end class SkipHours < SkipHoursBase - def to_rss(rss, channel) + 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_rss(rss, skipHours.hours) + hour.to_feed(rss, skipHours.hours) end end end class Hour < HourBase - def to_rss(rss, hours) + def to_feed(rss, hours) hour = Rss::Channel::SkipHours::Hour.new set = setup_values(hour) if set hours << hour - setup_other_elements(rss) + set_parent(hour, hours) + setup_other_elements(rss, hour) end end - def have_required_values? - @content + private + def required_variable_names + %w(content) end end end class Cloud < CloudBase - def to_rss(*args) + def to_feed(*args) end end class Categories < CategoriesBase - def to_rss(*args) + 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_rss(rss) + def to_feed(rss) image = Rss::Channel::Image.new set = setup_values(image) if set image.link = link rss.channel.image = image - setup_other_elements(rss) + set_parent(image, rss.channel) + setup_other_elements(rss, image) + elsif required_element? + raise NotSetError.new("maker.image", not_set_required_variables) end end - - def have_required_values? - @url and @title and link + + private + def required_variable_names + %w(url title link) + end + + def required_element? + true end end class Items < ItemsBase - def to_rss(rss) + def to_feed(rss) if rss.channel normalize.each do |item| - item.to_rss(rss) + item.to_feed(rss) end - setup_other_elements(rss) + setup_other_elements(rss, rss.items) end end class Item < ItemBase - def to_rss(rss) + def to_feed(rss) item = Rss::Channel::Item.new set = setup_values(item) - if set + if set or title.have_required_values? rss.items << item - setup_other_elements(rss) + 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 have_required_values? - @title and @link + super and title.have_required_values? + end + + private + def required_variable_names + %w(link) + end + + def not_set_required_variables + vars = super + vars << "title" unless title.have_required_values? + vars end class Guid < GuidBase - def to_rss(*args) + def to_feed(*args) end end - + class Enclosure < EnclosureBase - def to_rss(*args) + def to_feed(*args) end end - + class Source < SourceBase - def to_rss(*args) + 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_rss(*args) + 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_rss(rss) + def to_feed(rss) textInput = Rss::Channel::TextInput.new set = setup_values(textInput) if set rss.channel.textInput = textInput - setup_other_elements(rss) + set_parent(textInput, rss.channel) + setup_other_elements(rss, textInput) end end private - def have_required_values? - @title and @description and @name and @link + def required_variable_names + %w(title description name link) end end end - add_maker(filename_to_version(__FILE__), RSS09) - add_maker(filename_to_version(__FILE__) + "1", RSS09) + add_maker("0.9", RSS09) + add_maker("0.91", RSS09) + add_maker("rss0.91", RSS09) end end diff --git a/lib/rss/maker/1.0.rb b/lib/rss/maker/1.0.rb index 3e6542a007..12608ad94a 100644 --- a/lib/rss/maker/1.0.rb +++ b/lib/rss/maker/1.0.rb @@ -9,10 +9,11 @@ module RSS def initialize super("1.0") + @feed_type = "rss" end private - def make_rss + def make_feed RDF.new(@version, @encoding, @standalone) end @@ -25,43 +26,46 @@ module RSS class Channel < ChannelBase - def to_rss(rss) - set = false - if @about - channel = RDF::Channel.new(@about) - set = setup_values(channel) - if set + 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) + setup_other_elements(rss, channel) + else + raise NotSetError.new("maker.channel", _not_set_required_variables) end end - - if (!@about or !set) and variable_is_set? - raise NotSetError.new("maker.channel", not_set_required_variables) - end - end - - def have_required_values? - @about and @title and @link and @description end private def setup_items(rss) items = RDF::Channel::Items.new seq = items.Seq - @maker.items.normalize.each do |item| - seq.lis << RDF::Channel::Items::Seq::Li.new(item.link) + 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? - rss.channel.image = RDF::Channel::Image.new(@maker.image.url) + image = RDF::Channel::Image.new(@maker.image.url) + rss.channel.image = image + set_parent(image, rss.channel) end end @@ -69,15 +73,23 @@ module 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 title link description) + %w(about link) end - + + def not_set_required_variables + vars = super + vars << "description" unless description.have_required_values? + vars << "title" unless title.have_required_values? + vars + end + class SkipDays < SkipDaysBase - def to_rss(*args) + def to_feed(*args) end class Day < DayBase @@ -85,7 +97,7 @@ module RSS end class SkipHours < SkipHoursBase - def to_rss(*args) + def to_feed(*args) end class Hour < HourBase @@ -93,112 +105,330 @@ module RSS end class Cloud < CloudBase - def to_rss(*args) + def to_feed(*args) end end class Categories < CategoriesBase - def to_rss(*args) + 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_rss(rss) + def to_feed(rss) if @url image = RDF::Image.new(@url) set = setup_values(image) if set rss.image = image - setup_other_elements(rss) + set_parent(image, rss) + setup_other_elements(rss, image) end end end def have_required_values? - @url and @title and link and @maker.channel.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_rss(rss) + def to_feed(rss) if rss.channel normalize.each do |item| - item.to_rss(rss) + item.to_feed(rss) end - setup_other_elements(rss) + setup_other_elements(rss, rss.items) end end class Item < ItemBase - def to_rss(rss) - if @link - item = RDF::Item.new(@link) + 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 - setup_other_elements(rss) + 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 - def have_required_values? - @title and @link + 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.have_required_values? + vars + end end class Guid < GuidBase - def to_rss(*args) + def to_feed(*args) end end - + class Enclosure < EnclosureBase - def to_rss(*args) + def to_feed(*args) end end - + class Source < SourceBase - def to_rss(*args) + 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_rss(*args) + 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_rss(rss) + def to_feed(rss) if @link textinput = RDF::Textinput.new(@link) set = setup_values(textinput) if set rss.textinput = textinput - setup_other_elements(rss) + set_parent(textinput, rss) + setup_other_elements(rss, textinput) end end end def have_required_values? - @title and @description and @name and @link and - @maker.channel.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(filename_to_version(__FILE__), RSS10) + add_maker("1.0", RSS10) + add_maker("rss1.0", RSS10) end end diff --git a/lib/rss/maker/2.0.rb b/lib/rss/maker/2.0.rb index a958661614..d93ba94d4a 100644 --- a/lib/rss/maker/2.0.rb +++ b/lib/rss/maker/2.0.rb @@ -7,16 +7,13 @@ module RSS class RSS20 < RSS09 - def initialize(rss_version="2.0") + def initialize(feed_version="2.0") super end class Channel < RSS09::Channel - def have_required_values? - @title and @link and @description - end - + private def required_variable_names %w(title link description) end @@ -32,47 +29,64 @@ module RSS end class Cloud < RSS09::Channel::Cloud - def to_rss(rss, channel) + def to_feed(rss, channel) cloud = Rss::Channel::Cloud.new set = setup_values(cloud) if set channel.cloud = cloud - setup_other_elements(rss) + set_parent(cloud, channel) + setup_other_elements(rss, cloud) end end - def have_required_values? - @domain and @port and @path and - @registerProcedure and @protocol + private + def required_variable_names + %w(domain port path registerProcedure protocol) end end class Categories < RSS09::Channel::Categories - def to_rss(rss, channel) + def to_feed(rss, channel) @categories.each do |category| - category.to_rss(rss, channel) + category.to_feed(rss, channel) end end class Category < RSS09::Channel::Categories::Category - def to_rss(rss, channel) + def to_feed(rss, channel) category = Rss::Channel::Category.new set = setup_values(category) if set channel.categories << category - setup_other_elements(rss) + set_parent(category, channel) + setup_other_elements(rss, category) end end - - def have_required_values? - @content + + 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 @@ -84,85 +98,123 @@ module RSS end private + def required_variable_names + %w(title description) + end + def variables super + ["pubDate"] end class Guid < RSS09::Items::Item::Guid - def to_rss(rss, item) + def to_feed(rss, item) guid = Rss::Channel::Item::Guid.new set = setup_values(guid) if set item.guid = guid - setup_other_elements(rss) + set_parent(guid, item) + setup_other_elements(rss, guid) end end - - def have_required_values? - @content + + private + def required_variable_names + %w(content) end end class Enclosure < RSS09::Items::Item::Enclosure - def to_rss(rss, item) + def to_feed(rss, item) enclosure = Rss::Channel::Item::Enclosure.new set = setup_values(enclosure) if set item.enclosure = enclosure - setup_other_elements(rss) + set_parent(enclosure, item) + setup_other_elements(rss, enclosure) end end - - def have_required_values? - @url and @length and @type + + private + def required_variable_names + %w(url length type) end end class Source < RSS09::Items::Item::Source - def to_rss(rss, item) + def to_feed(rss, item) source = Rss::Channel::Item::Source.new set = setup_values(source) if set item.source = source - setup_other_elements(rss) + set_parent(source, item) + setup_other_elements(rss, source) end end - - def have_required_values? - @url and @content + + 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_rss(rss, item) + def to_feed(rss, item) @categories.each do |category| - category.to_rss(rss, item) + category.to_feed(rss, item) end end class Category < RSS09::Items::Item::Categories::Category - def to_rss(rss, item) + 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 - - def have_required_values? - @content + + 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(filename_to_version(__FILE__), RSS20) + add_maker("2.0", RSS20) + add_maker("rss2.0", RSS20) end end diff --git a/lib/rss/maker/atom.rb b/lib/rss/maker/atom.rb new file mode 100644 index 0000000000..27d30c6d89 --- /dev/null +++ b/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 uri= content= + end + set = setup_values(logo) + class << logo + undef 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/lib/rss/maker/base.rb b/lib/rss/maker/base.rb index 6d7dd557bf..ad47ff29cc 100644 --- a/lib/rss/maker/base.rb +++ b/lib/rss/maker/base.rb @@ -4,9 +4,7 @@ require 'rss/rss' module RSS module Maker - module Base - def self.append_features(klass) super @@ -46,28 +44,60 @@ module RSS NEED_INITIALIZE_VARIABLES end - def self.def_array_element(name) + def self.def_array_element(name, plural=nil, klass=nil) include Enumerable extend Forwardable - def_delegators("@\#{name}", :<<, :[], :[]=, :first, :last) - def_delegators("@\#{name}", :push, :pop, :shift, :unshift) - def_delegators("@\#{name}", :each, :size) - - add_need_initialize_variable(name, "[]") + plural ||= "\#{name}s" + klass ||= "self.class::\#{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(<<-EOM, __FILE__, __LINE__ + 1) + def new_\#{name} + \#{name} = \#{klass}.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 +EOM end EOC end + attr_reader :maker def initialize(maker) @maker = maker + @default_values_are_set = false initialize_variables end def have_required_values? - true + 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| @@ -75,16 +105,32 @@ module RSS end end - def setup_other_elements(rss) + def setup_other_elements(feed, current=nil) + current ||= current_element(feed) self.class.other_elements.each do |element| - __send__("setup_#{element}", rss, current_element(rss)) + __send__("setup_#{element}", feed, current) end end - def current_element(rss) - rss + 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? @@ -102,6 +148,10 @@ module RSS 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| "nil" == init @@ -110,10 +160,6 @@ module RSS end end - def variable_is_set? - variables.find {|var| !__send__(var).nil?} - end - def not_set_required_variables required_variable_names.find_all do |var| __send__(var).nil? @@ -126,7 +172,92 @@ module RSS 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 + 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 + + 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(<<-EOC, __FILE__, __LINE__ + 1) + include EnsureXMLContent + + %w(type content xml_content).each do |element| + attr element, element != "xml_content" + add_need_initialize_variable(element) + end + + def xml_content=(content) + @xml_content = ensure_xml_content(content) + end + + alias_method(:xhtml, :xml_content) + alias_method(:xhtml=, :xml_content=) +EOC + 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::Date.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 @@ -143,8 +274,8 @@ module RSS add_need_initialize_variable(element, "make_#{element}") module_eval(<<-EOC, __FILE__, __LINE__) private - def setup_#{element}(rss) - @#{element}.to_rss(rss) + def setup_#{element}(feed) + @#{element}.to_feed(feed) end def make_#{element} @@ -153,12 +284,15 @@ module RSS EOC end - attr_reader :rss_version + attr_reader :feed_version + alias_method(:rss_version, :feed_version) attr_accessor :version, :encoding, :standalone - - def initialize(rss_version) + + def initialize(feed_version) super(self) - @rss_version = rss_version + @feed_type = nil + @feed_subtype = nil + @feed_version = feed_version @version = "1.0" @encoding = "UTF-8" @standalone = nil @@ -167,19 +301,19 @@ EOC def make if block_given? yield(self) - to_rss + to_feed else nil end end - def to_rss - rss = make_rss - setup_xml_stylesheets(rss) - setup_elements(rss) - setup_other_elements(rss) - if rss.channel - rss + def to_feed + feed = make_feed + setup_xml_stylesheets(feed) + setup_elements(feed) + setup_other_elements(feed) + if feed.valid? + feed else nil end @@ -190,25 +324,12 @@ EOC def make_xml_stylesheets XMLStyleSheets.new(self) end - end class XMLStyleSheets include Base - def_array_element("xml_stylesheets") - - def to_rss(rss) - @xml_stylesheets.each do |xss| - xss.to_rss(rss) - end - end - - def new_xml_stylesheet - xss = XMLStyleSheet.new(@maker) - @xml_stylesheets << xss - xss - end + def_array_element("xml_stylesheet", nil, "XMLStyleSheet") class XMLStyleSheet include Base @@ -218,19 +339,15 @@ EOC add_need_initialize_variable(attribute) end - def to_rss(rss) + def to_feed(feed) xss = ::RSS::XMLStyleSheet.new guess_type_if_need(xss) set = setup_values(xss) if set - rss.xml_stylesheets << xss + feed.xml_stylesheets << xss end end - def have_required_values? - @href and @type - end - private def guess_type_if_need(xss) if @type.nil? @@ -238,20 +355,27 @@ EOC @type = xss.type end end + + def required_variable_names + %w(href type) + end end end class ChannelBase include Base + include SetupDefaultDate - %w(cloud categories skipDays skipHours).each do |element| + %w(cloud categories skipDays skipHours links authors + contributors generator copyright description + title).each do |element| attr_reader element add_other_element(element) add_need_initialize_variable(element, "make_#{element}") module_eval(<<-EOC, __FILE__, __LINE__) private - def setup_#{element}(rss, current) - @#{element}.to_rss(rss, current) + def setup_#{element}(feed, current) + @#{element}.to_feed(feed, current) end def make_#{element} @@ -260,34 +384,102 @@ EOC EOC end - %w(about title link description language copyright + %w(id about language managingEditor webMaster rating docs date - lastBuildDate generator ttl).each do |element| + lastBuildDate ttl).each do |element| attr_accessor element add_need_initialize_variable(element) end - alias_method(:pubDate, :date) - alias_method(:pubDate=, :date=) + def pubDate + date + end + + def pubDate=(date) + self.date = date + end + + def updated + date + end + + def updated=(date) + self.date = date + end + + def link + _link = links.first + _link ? _link.href : nil + end + + def link=(href) + _link = links.first || links.new_link + _link.rel = "self" + _link.href = href + end + + def author + _author = authors.first + _author ? _author.name : nil + end + + def author=(name) + _author = authors.first || authors.new_author + _author.name = name + end + + def contributor + _contributor = contributors.first + _contributor ? _contributor.name : nil + end + + def contributor=(name) + _contributor = contributors.first || contributors.new_contributor + _contributor.name = name + end + + def generator=(content) + @generator.content = content + end + + def copyright=(content) + @copyright.content = content + end + + alias_method(:rights, :copyright) + alias_method(:rights=, :copyright=) + + def description=(content) + @description.content = content + end + + alias_method(:subtitle, :description) + alias_method(:subtitle=, :description=) + + def title=(content) + @title.content = content + end - def current_element(rss) - rss.channel + 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 include Base - def_array_element("days") - - def new_day - day = self.class::Day.new(@maker) - @days << day - day - end - - def current_element(rss) - rss.channel.skipDays - end + def_array_element("day") class DayBase include Base @@ -296,28 +488,13 @@ EOC attr_accessor element add_need_initialize_variable(element) end - - def current_element(rss) - rss.channel.skipDays.last - end - end end class SkipHoursBase include Base - def_array_element("hours") - - def new_hour - hour = self.class::Hour.new(@maker) - @hours << hour - hour - end - - def current_element(rss) - rss.channel.skipHours - end + def_array_element("hour") class HourBase include Base @@ -326,11 +503,6 @@ EOC attr_accessor element add_need_initialize_variable(element) end - - def current_element(rss) - rss.channel.skipHours.last - end - end end @@ -341,33 +513,88 @@ EOC attr_accessor element add_need_initialize_variable(element) end - - def current_element(rss) - rss.channel.cloud - end - end class CategoriesBase include Base - - def_array_element("categories") - def new_category - category = self.class::Category.new(@maker) - @categories << category - category - end + def_array_element("category", "categories") class CategoryBase include Base - %w(domain content).each do |element| + %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 + include Base + + def_array_element("link") + + class LinkBase + include Base + + %w(href rel type hreflang title length).each do |element| + attr_accessor element + add_need_initialize_variable(element) + end + end + end + + class AuthorsBase + include Base + + def_array_element("author") + + class AuthorBase + include Base + include AtomPersonConstructBase + end + end + + class ContributorsBase + include Base + + def_array_element("contributor") + + class ContributorBase + include Base + include AtomPersonConstructBase + end + end + + class GeneratorBase + include Base + + %w(uri version content).each do |element| + attr_accessor element + add_need_initialize_variable(element) + end + end + + class CopyrightBase + include Base + include AtomTextConstructBase + end + + class DescriptionBase + include Base + include AtomTextConstructBase + end + + class TitleBase + include Base + include AtomTextConstructBase + end end class ImageBase @@ -377,21 +604,17 @@ EOC attr_accessor element add_need_initialize_variable(element) end - + def link @maker.channel.link end - - def current_element(rss) - rss.image - end end class ItemsBase include Base - def_array_element("items") - + def_array_element("item") + attr_accessor :do_sort, :max_size def initialize(maker) @@ -407,17 +630,7 @@ EOC sort_if_need[0..@max_size] end end - - def current_element(rss) - rss.items - end - def new_item - item = self.class::Item.new(@maker) - @items << item - item - end - private def sort_if_need if @do_sort.respond_to?(:call) @@ -435,15 +648,17 @@ EOC class ItemBase include Base - - %w(guid enclosure source categories).each do |element| + include SetupDefaultDate + + %w(guid enclosure source categories authors links + contributors rights description content title).each do |element| attr_reader element add_other_element(element) add_need_initialize_variable(element, "make_#{element}") module_eval(<<-EOC, __FILE__, __LINE__) private - def setup_#{element}(rss, current) - @#{element}.to_rss(rss, current) + def setup_#{element}(feed, current) + @#{element}.to_feed(feed, current) end def make_#{element} @@ -451,30 +666,77 @@ EOC end EOC end - - %w(title link description date author comments).each do |element| + + %w(date comments id published).each do |element| attr_accessor element add_need_initialize_variable(element) end - alias_method(:pubDate, :date) - alias_method(:pubDate=, :date=) + def pubDate + date + end + + def pubDate=(date) + self.date = date + end + + def updated + date + end + + def updated=(date) + self.date = date + end + + def author + _link = authors.first + _link ? _author.name : nil + end + + def author=(name) + _author = authors.first || authors.new_author + _author.name = name + end + + def link + _link = links.first + _link ? _link.href : nil + end + + def link=(href) + _link = links.first || links.new_link + _link.rel = "alternate" + _link.href = href + end + + def rights=(content) + @rights.content = content + end + + def description=(content) + @description.content = content + end + + alias_method(:summary, :description) + alias_method(:summary=, :description=) + + def title=(content) + @title.content = content + end def <=>(other) - if date and other.date - date <=> other.date - elsif date + _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 + elsif _other_date -1 else 0 end end - - def current_element(rss) - rss.items.last - end class GuidBase include Base @@ -484,7 +746,7 @@ EOC add_need_initialize_variable(element) end end - + class EnclosureBase include Base @@ -493,18 +755,168 @@ EOC add_need_initialize_variable(element) end end - + class SourceBase include Base - %w(url content).each do |element| + %w(authors categories contributors generator icon + links logo rights subtitle title).each do |element| + attr_reader element + add_other_element(element) + add_need_initialize_variable(element, "make_#{element}") + module_eval(<<-EOC, __FILE__, __LINE__) + private + def setup_#{element}(feed, current) + @#{element}.to_feed(feed, current) + end + + def make_#{element} + self.class::#{Utils.to_class_name(element)}.new(@maker) + end + EOC + end + + %w(id content date).each do |element| attr_accessor element add_need_initialize_variable(element) end + + def url + link = links.first + link ? link.href : nil + end + + def url=(value) + link = links.first || links.new_link + link.href = value + end + + 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 + include Base + + %w(url).each do |element| + attr_accessor element + add_need_initialize_variable(element) + end + end + + LinksBase = ChannelBase::LinksBase + + class LogoBase + include Base + + %w(uri).each do |element| + attr_accessor element + add_need_initialize_variable(element) + end + end + + class RightsBase + include Base + include AtomTextConstructBase + end + + class SubtitleBase + include Base + include AtomTextConstructBase + end + + class TitleBase + include Base + include AtomTextConstructBase + end end - + CategoriesBase = ChannelBase::CategoriesBase - + AuthorsBase = ChannelBase::AuthorsBase + LinksBase = ChannelBase::LinksBase + ContributorsBase = ChannelBase::ContributorsBase + + class RightsBase + include Base + include AtomTextConstructBase + end + + class DescriptionBase + include Base + include AtomTextConstructBase + end + + class ContentBase + include Base + include AtomTextConstructBase::EnsureXMLContent + + %w(type src content xml_content).each do |element| + attr element, element != "xml_content" + add_need_initialize_variable(element) + end + + def xml_content=(content) + content = ensure_xml_content(content) if inline_xhtml? + @xml_content = content + end + + alias_method(:xhtml, :xml_content) + alias_method(:xhtml=, :xml_content=) + + alias_method(:xml, :xml_content) + alias_method(:xml=, :xml_content=) + + private + 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 + include Base + include AtomTextConstructBase + end end end @@ -515,12 +927,6 @@ EOC attr_accessor element add_need_initialize_variable(element) end - - def current_element(rss) - rss.textinput - end - end - end end diff --git a/lib/rss/maker/dublincore.rb b/lib/rss/maker/dublincore.rb index b208d5fcb2..088ae60942 100644 --- a/lib/rss/maker/dublincore.rb +++ b/lib/rss/maker/dublincore.rb @@ -24,8 +24,8 @@ module RSS #{full_plural_klass_name}.new(@maker) end - def setup_#{full_plural_name}(rss, current) - @#{full_plural_name}.to_rss(rss, current) + def setup_#{full_plural_name}(feed, current) + @#{full_plural_name}.to_feed(feed, current) end def #{full_name} @@ -36,6 +36,17 @@ module RSS @#{full_plural_name}[0] = #{full_klass_name}.new(self) @#{full_plural_name}[0].value = new_value end + + def new_#{full_name}(value=nil) + #{full_name} = #{full_klass_name}.new(self) + #{full_name}.value = value + @#{full_plural_name} << #{full_name} + if block_given? + yield #{full_name} + else + #{full_name} + end + end EOC end @@ -48,25 +59,14 @@ EOC ::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)}" module_eval(<<-EOC, __FILE__, __LINE__) class #{plural_klass_name}Base include Base - def_array_element(#{plural_name.dump}) - - def new_#{name} - #{name} = self.class::#{klass_name}.new(self) - @#{plural_name} << #{name} - #{name} - end + def_array_element(#{name.dump}, #{plural_name.dump}) - def to_rss(rss, current) - @#{plural_name}.each do |#{name}| - #{name}.to_rss(rss, current) - end - end - class #{klass_name}Base include Base @@ -78,6 +78,13 @@ EOC def have_required_values? @value end + + def to_feed(feed, current) + if value and current.respond_to?(:dc_#{name}) + new_item = current.class::#{full_klass_name}.new(value) + current.dc_#{plural_name} << new_item + end + end end end EOC @@ -88,16 +95,9 @@ EOC plural_name ||= "#{name}s" klass_name = Utils.to_class_name(name) plural_klass_name = "DublinCore#{Utils.to_class_name(plural_name)}" - full_klass_name = "DublinCore#{klass_name}" - klass.module_eval(<<-EOC, *Utils.get_file_and_line_from_caller(1)) + klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1) class #{plural_klass_name} < #{plural_klass_name}Base class #{klass_name} < #{klass_name}Base - def to_rss(rss, current) - if value and current.respond_to?(:dc_#{name}) - new_item = current.class::#{full_klass_name}.new(value) - current.dc_#{plural_name} << new_item - end - end end end EOC @@ -107,64 +107,36 @@ EOC class ChannelBase include DublinCoreModel - - remove_method(:date) - remove_method(:date=) - alias_method(:date, :dc_date) - alias_method(:date=, :dc_date=) end class ImageBase; include DublinCoreModel; end class ItemsBase class ItemBase include DublinCoreModel - - remove_method(:date) - remove_method(:date=) - alias_method(:date, :dc_date) - alias_method(:date=, :dc_date=) end end class TextinputBase; include DublinCoreModel; end - class RSS10 - class Channel - DublinCoreModel.install_dublin_core(self) - end - - class Image - DublinCoreModel.install_dublin_core(self) - end - - class Items - class Item + makers.each do |maker| + maker.module_eval(<<-EOC, __FILE__, __LINE__ + 1) + class Channel DublinCoreModel.install_dublin_core(self) end - end - class Textinput - DublinCoreModel.install_dublin_core(self) - end - end - - class RSS09 - class Channel - DublinCoreModel.install_dublin_core(self) - end + class Image + 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 Items - class Item + class Textinput DublinCoreModel.install_dublin_core(self) end - end - - class Textinput - DublinCoreModel.install_dublin_core(self) - end + EOC end end end diff --git a/lib/rss/maker/entry.rb b/lib/rss/maker/entry.rb new file mode 100644 index 0000000000..baa22c5bf1 --- /dev/null +++ b/lib/rss/maker/entry.rb @@ -0,0 +1,167 @@ +require "rss/maker/atom" +require "rss/maker/feed" + +module RSS + module Maker + module Atom + class Entry < RSSBase + def initialize + super("1.0") + @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 + + def have_required_values? + set_default_values do + super and title.have_required_values? + 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.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? + @rights = @maker.channel.rights + end + @title = @maker.channel.title unless keep[:title].variable_is_set? + 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", Atom::Entry) + add_maker("atom1.0:entry", Atom::Entry) + end +end diff --git a/lib/rss/maker/feed.rb b/lib/rss/maker/feed.rb new file mode 100644 index 0000000000..ac26788102 --- /dev/null +++ b/lib/rss/maker/feed.rb @@ -0,0 +1,429 @@ +require "rss/maker/atom" + +module RSS + module Maker + module Atom + class Feed < RSSBase + def initialize + super("1.0") + @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.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 url= content= + end + set = setup_values(logo) + class << logo + undef 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.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.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 url= content= + end + set = setup_values(icon) + class << icon + undef 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", Atom::Feed) + add_maker("atom:feed", Atom::Feed) + add_maker("atom1.0", Atom::Feed) + add_maker("atom1.0:feed", Atom::Feed) + end +end diff --git a/lib/rss/maker/image.rb b/lib/rss/maker/image.rb index ed51c8ecba..e3469d0597 100644 --- a/lib/rss/maker/image.rb +++ b/lib/rss/maker/image.rb @@ -11,11 +11,11 @@ module RSS name = "#{RSS::IMAGE_PREFIX}_item" klass.add_need_initialize_variable(name, "make_#{name}") klass.add_other_element(name) - klass.module_eval(<<-EOC, __FILE__, __LINE__+1) + klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1) attr_reader :#{name} - def setup_#{name}(rss, current) + def setup_#{name}(feed, current) if @#{name} - @#{name}.to_rss(rss, current) + @#{name}.to_feed(feed, current) end end @@ -25,6 +25,14 @@ module RSS EOC 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 include Base include Maker::DublinCoreModel @@ -42,6 +50,15 @@ EOC 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 @@ -54,9 +71,9 @@ EOC klass.add_other_element(name) klass.module_eval(<<-EOC, __FILE__, __LINE__+1) attr_reader :#{name} - def setup_#{name}(rss, current) + def setup_#{name}(feed, current) if @#{name} - @#{name}.to_rss(rss, current) + @#{name}.to_feed(feed, current) end end @@ -66,6 +83,14 @@ EOC EOC 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 include Base include Maker::DublinCoreModel @@ -79,6 +104,15 @@ EOC 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 @@ -88,58 +122,18 @@ EOC class ItemBase; include Maker::ImageItemModel; end end - class RSS10 - class Items - class Item - class ImageItem < ImageItemBase - DublinCoreModel.install_dublin_core(self) - def to_rss(rss, current) - if @about - item = ::RSS::ImageItemModel::ImageItem.new(@about, @resource) - setup_values(item) - setup_other_elements(item) - current.image_item = item - end - end - end + makers.each do |maker| + maker.module_eval(<<-EOC, __FILE__, __LINE__ + 1) + class Channel + ImageFaviconModel.install_image_favicon(self) end - end - - class Channel - class ImageFavicon < ImageFaviconBase - DublinCoreModel.install_dublin_core(self) - def to_rss(rss, current) - if @about and @image_size - args = [@about, @image_size] - favicon = ::RSS::ImageFaviconModel::ImageFavicon.new(*args) - setup_values(favicon) - setup_other_elements(favicon) - current.image_favicon = favicon - end - end - end - end - end - class RSS09 - class Items - class Item - class ImageItem < ImageItemBase - DublinCoreModel.install_dublin_core(self) - def to_rss(*args) - end + class Items + class Item + ImageItemModel.install_image_item(self) end end - end - - class Channel - class ImageFavicon < ImageFaviconBase - DublinCoreModel.install_dublin_core(self) - def to_rss(*args) - end - end - end + EOC end - end end diff --git a/lib/rss/maker/taxonomy.rb b/lib/rss/maker/taxonomy.rb index 2e54ea66eb..2e53a4e1f4 100644 --- a/lib/rss/maker/taxonomy.rb +++ b/lib/rss/maker/taxonomy.rb @@ -15,17 +15,17 @@ module RSS def make_taxo_topics self.class::TaxonomyTopics.new(@maker) end - - def setup_taxo_topics(rss, current) - @taxo_topics.to_rss(rss, current) + + def setup_taxo_topics(feed, current) + @taxo_topics.to_feed(feed, current) end EOC end def self.install_taxo_topics(klass) - klass.module_eval(<<-EOC, *Utils.get_file_and_line_from_caller(1)) + klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1) class TaxonomyTopics < TaxonomyTopicsBase - def to_rss(rss, current) + def to_feed(feed, current) if current.respond_to?(:taxo_topics) topics = current.class::TaxonomyTopics.new bag = topics.Bag @@ -43,7 +43,8 @@ EOC include Base attr_reader :resources - def_array_element("resources") + def_array_element("resource") + remove_method :new_resource end end @@ -59,8 +60,8 @@ EOC self.class::TaxonomyTopics.new(@maker) end - def setup_taxo_topics(rss, current) - @taxo_topics.to_rss(rss, current) + def setup_taxo_topics(feed, current) + @taxo_topics.to_feed(feed, current) end def taxo_topic @@ -75,25 +76,21 @@ EOC end def self.install_taxo_topic(klass) - klass.module_eval(<<-EOC, *Utils.get_file_and_line_from_caller(1)) + 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_rss(rss, current) + def to_feed(feed, current) if current.respond_to?(:taxo_topics) topic = current.class::TaxonomyTopic.new(value) topic.taxo_link = value - taxo_topics.to_rss(rss, topic) if taxo_topics + taxo_topics.to_feed(feed, topic) if taxo_topics current.taxo_topics << topic - setup_other_elements(rss) + setup_other_elements(feed, topic) end end - - def current_element(rss) - super.taxo_topics.last - end end end EOC @@ -102,20 +99,8 @@ EOC class TaxonomyTopicsBase include Base - def_array_element("taxo_topics") - - def new_taxo_topic - taxo_topic = self.class::TaxonomyTopic.new(self) - @taxo_topics << taxo_topic - taxo_topic - end + def_array_element("taxo_topic", nil, "self.class::TaxonomyTopic") - def to_rss(rss, current) - @taxo_topics.each do |taxo_topic| - taxo_topic.to_rss(rss, current) - end - end - class TaxonomyTopicBase include Base include DublinCoreModel @@ -147,32 +132,20 @@ EOC end end - class RSS10 - TaxonomyTopicModel.install_taxo_topic(self) - - class Channel - TaxonomyTopicsModel.install_taxo_topics(self) - end + makers.each do |maker| + maker.module_eval(<<-EOC, __FILE__, __LINE__ + 1) + TaxonomyTopicModel.install_taxo_topic(self) - class Items - class Item + class Channel TaxonomyTopicsModel.install_taxo_topics(self) end - end - end - - class RSS09 - TaxonomyTopicModel.install_taxo_topic(self) - - class Channel - TaxonomyTopicsModel.install_taxo_topics(self) - end - class Items - class Item - TaxonomyTopicsModel.install_taxo_topics(self) + class Items + class Item + TaxonomyTopicsModel.install_taxo_topics(self) + end end - end + EOC end end end diff --git a/lib/rss/maker/trackback.rb b/lib/rss/maker/trackback.rb index 32254a040c..09a2fceb2d 100644 --- a/lib/rss/maker/trackback.rb +++ b/lib/rss/maker/trackback.rb @@ -11,9 +11,9 @@ module RSS name = "#{RSS::TRACKBACK_PREFIX}_ping" klass.add_need_initialize_variable(name) klass.add_other_element(name) - klass.module_eval(<<-EOC, __FILE__, __LINE__+1) + klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1) attr_accessor :#{name} - def setup_#{name}(rss, current) + def setup_#{name}(feed, current) if #{name} and current.respond_to?(:#{name}=) current.#{name} = #{name} end @@ -23,14 +23,14 @@ module RSS name = "#{RSS::TRACKBACK_PREFIX}_abouts" klass.add_need_initialize_variable(name, "make_#{name}") klass.add_other_element(name) - klass.module_eval(<<-EOC, __FILE__, __LINE__+1) + klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1) attr_accessor :#{name} def make_#{name} self.class::TrackBackAbouts.new(self) end - def setup_#{name}(rss, current) - @#{name}.to_rss(rss, current) + def setup_#{name}(feed, current) + @#{name}.to_feed(feed, current) end EOC end @@ -38,20 +38,8 @@ module RSS class TrackBackAboutsBase include Base - def_array_element("abouts") - - def new_about - about = self.class::TrackBackAbout.new(@maker) - @abouts << about - about - end + def_array_element("about", nil, "self.class::TrackBackAbout") - def to_rss(rss, current) - @abouts.each do |about| - about.to_rss(rss, current) - end - end - class TrackBackAboutBase include Base @@ -62,65 +50,38 @@ module RSS alias_method(:resource=, :value=) alias_method(:content, :value) alias_method(:content=, :value=) - + def have_required_values? @value end - - end - end - end - - class ItemsBase - class ItemBase; include TrackBackModel; end - end - class RSS10 - class Items - class Item - class TrackBackAbouts < TrackBackAboutsBase - class TrackBackAbout < TrackBackAboutBase - def to_rss(rss, current) - if resource - about = ::RSS::TrackBackModel10::TrackBackAbout.new(resource) - current.trackback_abouts << about - end - 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 RSS09 - class Items - class Item - class TrackBackAbouts < TrackBackAboutsBase - def to_rss(*args) - end - class TrackBackAbout < TrackBackAboutBase - end - end - end - end + class ItemsBase + class ItemBase; include TrackBackModel; end end - - class RSS20 - class Items - class Item - class TrackBackAbouts < TrackBackAboutsBase - class TrackBackAbout < TrackBackAboutBase - def to_rss(rss, current) - if content - about = ::RSS::TrackBackModel20::TrackBackAbout.new(content) - current.trackback_abouts << about - 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 - end + EOC end - end end diff --git a/lib/rss/parser.rb b/lib/rss/parser.rb index babf15f52c..f5ea2bbc03 100644 --- a/lib/rss/parser.rb +++ b/lib/rss/parser.rb @@ -2,6 +2,7 @@ require "forwardable" require "open-uri" require "rss/rss" +require "rss/xml" module RSS @@ -63,7 +64,8 @@ module RSS end end - def parse(rss, do_validate=true, ignore_unknown_element=true, parser_class=default_parser) + 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 @@ -103,7 +105,7 @@ module RSS return rss if rss.is_a?(::URI::Generic) begin - URI(rss) + ::URI.parse(rss) rescue ::URI::Error rss end @@ -158,26 +160,26 @@ module RSS class << self - @@setters = {} + @@accessor_bases = {} @@registered_uris = {} @@class_names = {} - def install_setter(uri, tag_name, setter) - @@setters[uri] ||= {} - @@setters[uri][tag_name] = setter - end - def setter(uri, tag_name) - begin - @@setters[uri][tag_name] - rescue NameError + _getter = getter(uri, tag_name) + if _getter + "#{_getter}=" + else nil end end + def getter(uri, tag_name) + (@@accessor_bases[uri] || {})[tag_name] + end + def available_tags(uri) begin - @@setters[uri].keys + @@accessor_bases[uri].keys rescue NameError [] end @@ -205,8 +207,8 @@ module RSS end end - def install_get_text_element(uri, name, setter) - install_setter(uri, name, setter) + 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 @@ -215,20 +217,18 @@ module RSS end private + 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, name, file, line) register_uri(uri, name) unless private_instance_methods(false).include?("start_#{name}".to_sym) module_eval(<<-EOT, file, line) def start_#{name}(name, prefix, attrs, ns) - uri = ns[prefix] + uri = _ns(ns, prefix) if self.class.uri_registered?(uri, #{name.inspect}) - if @do_validate - tags = self.class.available_tags(uri) - unless tags.include?(name) - raise UnknownTagError.new(name, uri) - end - end start_get_text_element(name, prefix, ns, uri) else start_else_element(name, prefix, attrs, ns) @@ -244,7 +244,6 @@ module RSS end module ListenerMixin - attr_reader :rss attr_accessor :ignore_unknown_element @@ -254,13 +253,16 @@ module RSS @rss = nil @ignore_unknown_element = true @do_validate = true - @ns_stack = [{}] + @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 def xmldecl(version, encoding, standalone) @@ -271,7 +273,7 @@ module RSS if name == "xml-stylesheet" params = parse_pi_content(content) if params.has_key?("href") - @xml_stylesheets << XMLStyleSheet.new(*params) + @xml_stylesheets << XMLStyleSheet.new(params) end end end @@ -291,12 +293,41 @@ module RSS @ns_stack.push(ns) prefix, local = split_name(name) - @tag_stack.last.push([ns[prefix], local]) + @tag_stack.last.push([_ns(ns, prefix), local]) @tag_stack.push([]) - if respond_to?("start_#{local}", true) - __send__("start_#{local}", local, prefix, attrs, ns.dup) + 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 - start_else_element(local, prefix, attrs, ns.dup) + 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 @@ -313,10 +344,17 @@ module RSS end def text(data) - @text_stack.last << 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/ def parse_pi_content(content) @@ -328,20 +366,20 @@ module RSS end def start_else_element(local, prefix, attrs, ns) - class_name = self.class.class_name(ns[prefix], local) + class_name = self.class.class_name(_ns(ns, prefix), local) current_class = @last_element.class if current_class.const_defined?(class_name) next_class = current_class.const_get(class_name) start_have_something_element(local, prefix, attrs, ns, next_class) else - if @ignore_unknown_element + 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 NotExceptedTagError.new(local, parent) + raise NotExpectedTagError.new(local, _ns(ns, prefix), parent) end end end @@ -353,41 +391,48 @@ module RSS end def check_ns(tag_name, prefix, ns, require_uri) - if @do_validate - if ns[prefix] == require_uri - #ns.delete(prefix) - else + 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) - @proc_stack.push Proc.new {|text, tags| + pr = Proc.new do |text, tags| setter = self.class.setter(required_uri, tag_name) - setter ||= "#{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 not @ignore_unknown_element - raise NotExceptedTagError.new(tag_name, @last_element.tag_name) + 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) - args = [] - - klass.get_attributes.each do |a_name, a_uri, required| + 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 == [nil] + unless a_uri == [""] for prefix, uri in ns if a_uri.include?(uri) val = attrs["#{prefix}:#{a_name}"] @@ -395,12 +440,12 @@ module RSS end end end - if val.nil? and a_uri.include?(nil) + if val.nil? and a_uri.include?("") val = attrs[a_name] end if @do_validate and required and val.nil? - unless a_uri.include?(nil) + unless a_uri.include?("") for prefix, uri in ns if a_uri.include?(uri) a_name = "#{prefix}:#{a_name}" @@ -410,20 +455,37 @@ module RSS raise MissingAttributeError.new(tag_name, a_name) end - args << val + attributes[a_name] = val end previous = @last_element - next_element = klass.new(*args) - next_element.do_validate = @do_validate - previous.funcall(:set_next_element, tag_name, next_element) + next_element = klass.new(@do_validate, attributes) + previous.__send!(:set_next_element, tag_name, next_element) @last_element = next_element - @proc_stack.push Proc.new { |text, tags| + @last_element.parent = previous if klass.need_parent? + @xml_child_mode = @last_element.have_xml_content? + pr = Proc.new do |text, tags| p(@last_element.class) if DEBUG - @last_element.content = text if klass.have_content? - @last_element.validate_for_stream(tags) if @do_validate + 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 = Base64.decode64(text.lstrip) + end + @last_element.content = text + end + end + if @do_validate + @last_element.validate_for_stream(tags, @ignore_unknown_element) + end @last_element = previous - } + end + @proc_stack.push(pr) end end diff --git a/lib/rss/rss.rb b/lib/rss/rss.rb index 52ca4db890..c0ce96d6bf 100644 --- a/lib/rss/rss.rb +++ b/lib/rss/rss.rb @@ -11,11 +11,19 @@ class Time (\.\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] - datetime << $7.to_f * 1000000 if $7 - if $8 - Time.utc(*datetime) - zone_offset($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 @@ -26,7 +34,14 @@ class Time end unless method_defined?(:w3cdtf) - alias w3cdtf iso8601 + 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 @@ -38,7 +53,7 @@ require "rss/xml-stylesheet" module RSS - VERSION = "0.1.6" + VERSION = "0.1.7" URI = "http://purl.org/rss/1.0/" @@ -87,13 +102,15 @@ module RSS end end - class NotExceptedTagError < InvalidRSSError - attr_reader :tag, :parent - def initialize(tag, parent) - @tag, @parent = tag, parent - super("tag <#{tag}> is not expected in tag <#{parent}>") + 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 @@ -135,15 +152,27 @@ module RSS 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(name) + 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) - attr_accessor 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} @@ -156,11 +185,13 @@ EOC end alias_method(:install_have_attribute_element, :install_have_child_element) - def install_have_children_element(name, plural_name=nil) + 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) + def_children_accessor(name, plural_name) install_element(name, "s") do |n, elem_name| <<-EOC @@ -174,9 +205,12 @@ EOC end end - def install_text_element(name, type=nil, disp_name=name) + def install_text_element(tag_name, uri, occurs, name=nil, type=nil, disp_name=nil) + name ||= tag_name + disp_name ||= name self::ELEMENTS << name add_need_initialize_variable(name) + install_model(tag_name, uri, occurs, name) def_corresponded_attr_writer name, type, disp_name convert_attr_reader name @@ -199,9 +233,13 @@ EOC end end - def install_date_element(name, type, disp_name=name) + 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 @@ -230,34 +268,76 @@ EOC 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 #{name}_element#{postfix}(need_convert=true, indent='') + def #{method_name}(need_convert=true, indent='') #{yield(name, elem_name)} end - private :#{name}_element#{postfix} + private :#{method_name} EOC end - def convert_attr_reader(*attrs) + 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} - if @converter - @converter.convert(@#{attr}) + value = #{attr}_without_base + return nil if value.nil? + if /\\A[a-z][a-z0-9+.\\-]*:/i =~ value + value else - @#{attr} + "\#{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 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? or new_value.kind_of?(Time) + if new_value.nil? @#{name} = new_value + elsif new_value.kind_of?(Time) + @#{name} = new_value.dup else if @do_validate begin @@ -269,7 +349,9 @@ EOC @#{name} = nil if /\\A\\s*\\z/ !~ new_value.to_s begin - @#{name} = Time.parse(new_value) + unless Date._parse(new_value, false).empty? + @#{name} = Time.parse(new_value) + end rescue ArgumentError end end @@ -350,6 +432,32 @@ EOC 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 def_children_accessor(accessor_name, plural_name) module_eval(<<-EOC, *get_file_and_line_from_caller(2)) def #{plural_name} @@ -365,10 +473,12 @@ EOC end def #{accessor_name}=(*args) + receiver = self.class.name warn("Warning:\#{caller.first.sub(/:in `.*'\z/, '')}: " \ - "Don't use `#{accessor_name} = XXX'/`set_#{accessor_name}(XXX)'. " \ + "Don't use `\#{receiver}\##{accessor_name} = XXX'/" \ + "`\#{receiver}\#set_#{accessor_name}(XXX)'. " \ "Those APIs are not sense of Ruby. " \ - "Use `#{plural_name} << XXX' instead of them.") + "Use `\#{receiver}\##{plural_name} << XXX' instead of them.") if args.size == 1 @#{accessor_name}.push(args[0]) else @@ -378,36 +488,70 @@ EOC alias_method(:set_#{accessor_name}, :#{accessor_name}=) EOC end + end - def def_content_only_to_s - module_eval(<<-EOC, *get_file_and_line_from_caller(2)) - def to_s(need_convert=true, indent=calc_indent) - if @content - rv = tag(indent) do |next_indent| - h(@content) - end - rv = convert(rv) if need_convert - rv + 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) + 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 -EOC end - end class Element extend BaseModel include Utils + include SetupMaker INDENT = " " MUST_CALL_VALIDATORS = {} - MODEL = [] + MODELS = [] GET_ATTRIBUTES = [] HAVE_CHILDREN_ELEMENTS = [] + TO_ELEMENT_METHODS = [] NEED_INITIALIZE_VARIABLES = [] PLURAL_FORMS = {} @@ -416,8 +560,8 @@ EOC def must_call_validators MUST_CALL_VALIDATORS end - def model - MODEL + def models + MODELS end def get_attributes GET_ATTRIBUTES @@ -425,6 +569,9 @@ EOC def have_children_elements HAVE_CHILDREN_ELEMENTS end + def to_element_methods + TO_ELEMENT_METHODS + end def need_initialize_variables NEED_INITIALIZE_VARIABLES end @@ -435,9 +582,10 @@ EOC def inherited(klass) klass.const_set("MUST_CALL_VALIDATORS", {}) - klass.const_set("MODEL", []) + 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", {}) @@ -446,14 +594,13 @@ EOC @tag_name = name.split(/::/).last @tag_name[0,1] = @tag_name[0,1].downcase - @indent_size = name.split(/::/).size - 2 @have_content = false def self.must_call_validators super.merge(MUST_CALL_VALIDATORS) end - def self.model - MODEL + super + def self.models + MODELS + super end def self.get_attributes GET_ATTRIBUTES + super @@ -461,6 +608,9 @@ EOC def self.have_children_elements HAVE_CHILDREN_ELEMENTS + super end + def self.to_element_methods + TO_ELEMENT_METHODS + super + end def self.need_initialize_variables NEED_INITIALIZE_VARIABLES + super end @@ -473,25 +623,32 @@ EOC MUST_CALL_VALIDATORS[uri] = prefix end - def self.install_model(tag, occurs=nil) - if m = MODEL.find {|t, o| t == tag} - m[1] = occurs + def self.install_model(tag, uri, occurs=nil, getter=nil) + getter ||= tag + if m = MODELS.find {|t, u, o, g| t == tag and u == uri} + m[2] = occurs else - MODEL << [tag, occurs] + MODELS << [tag, uri, occurs, getter] end end def self.install_get_attribute(name, uri, required=true, - type=nil, disp_name=name) - def_corresponded_attr_writer name, type, disp_name - convert_attr_reader name + 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 - GET_ATTRIBUTES << [name, uri, required] + GET_ATTRIBUTES << [name, uri, required, element_name] + add_need_initialize_variable(disp_name) end - def self.def_corresponded_attr_writer(name, type=nil, disp_name=name) + def self.def_corresponded_attr_writer(name, type=nil, disp_name=nil) + disp_name ||= name case type when :integer integer_writer name, disp_name @@ -499,15 +656,32 @@ EOC 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 else attr_writer name end end - def self.content_setup(type=nil) - def_corresponded_attr_writer "content", type - convert_attr_reader :content - def_content_only_to_s + def self.def_corresponded_attr_reader(name, type=nil) + case type + when :inherit + inherit_convert_attr_reader name + when :uri + uri_convert_attr_reader name + else + convert_attr_reader name + end + end + + def self.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 @@ -519,6 +693,10 @@ EOC HAVE_CHILDREN_ELEMENTS << [variable_name, plural_name] end + def self.add_to_element_method(method_name) + TO_ELEMENT_METHODS << method_name + end + def self.add_need_initialize_variable(variable_name) NEED_INITIALIZE_VARIABLES << variable_name end @@ -535,7 +713,11 @@ EOC end def required_uri - nil + "" + end + + def need_parent? + false end def install_ns(prefix, uri) @@ -548,19 +730,18 @@ EOC def tag_name @tag_name end - - def indent_size - @indent_size - end - end - attr_accessor :do_validate + attr_accessor :parent, :do_validate - def initialize(do_validate=true) + 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 + initialize_variables(attrs || {}) end def tag_name @@ -571,10 +752,6 @@ EOC tag_name end - def indent_size - self.class.indent_size - end - def converter=(converter) @converter = converter targets = children.dup @@ -593,33 +770,73 @@ EOC value end end - - def validate + + 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 + __validate(ignore_unknown_element) + ensure + @do_validate = do_validate end - def validate_for_stream(tags) + def validate_for_stream(tags, ignore_unknown_element=true) validate_attribute - __validate(tags, false) + __validate(ignore_unknown_element, tags, false) end - def setup_maker(maker) - target = maker_target(maker) - unless target.nil? - setup_maker_attributes(target) - setup_maker_element(target) - setup_maker_elements(target) + 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 + private - def initialize_variables + 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| - instance_eval("@#{variable_name} = nil") + value = normalized_attrs[variable_name.to_s] + if value + __send__("#{variable_name}=", value) + else + instance_eval("@#{variable_name} = nil") + end end initialize_have_children_elements - @content = "" if self.class.have_content? + @content = normalized_attrs["content"] if self.class.have_content? end def initialize_have_children_elements @@ -628,14 +845,16 @@ EOC end end - def tag(indent, additional_attrs=[], &block) + def tag(indent, additional_attrs={}, &block) next_indent = indent + INDENT attrs = collect_attrs return "" if attrs.nil? - attrs += additional_attrs - start_tag = make_start_tag(indent, next_indent, attrs) + 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) @@ -650,6 +869,7 @@ EOC else content = content.reject{|x| x.empty?} if content.empty? + return "" if attrs.empty? end_tag = "/>" else start_tag << ">\n" @@ -671,58 +891,24 @@ EOC end def collect_attrs - _attrs.collect do |name, required, alias_name| + attrs = {} + _attrs.each do |name, required, alias_name| value = __send__(alias_name || name) return nil if required and value.nil? - [name, value] - end.reject do |name, value| - 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 - - def calc_indent - INDENT * (self.class.indent_size) - end - 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) - 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 + # For backward compatibility + def calc_indent + '' end def set_next_element(tag_name, next_element) @@ -737,22 +923,41 @@ EOC __send__("#{prefix}#{tag_name}=", next_element) end end - - # not String class children. + 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 - # default #validate() argument. def _tags - [] + rv = [] + self.class.models.each do |name, uri, occurs, getter| + value = __send__(getter) + next if value.nil? + if 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(tags=_tags, recursive=true) + def __validate(ignore_unknown_element, tags=_tags, recursive=true) if recursive children.compact.each do |child| child.validate @@ -761,54 +966,44 @@ EOC must_call_validators = self.class.must_call_validators tags = tag_filter(tags.dup) p tags if DEBUG - self.class::NSPOOL.each do |prefix, uri| - if tags.has_key?(uri) and !must_call_validators.has_key?(uri) - meth = "#{prefix}_validate" - __send__(meth, tags[uri]) if respond_to?(meth, true) - end - end must_call_validators.each do |uri, prefix| - __send__("#{prefix}_validate", tags[uri]) + _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| - if required and __send__(alias_name || a_name).nil? + 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 other_element(need_convert, indent='') - rv = [] - private_methods.each do |meth| - if /\A([^_]+)_[^_]+_elements?\z/ =~ meth.to_s and - self.class::NSPOOL.has_key?($1) - res = __send__(meth, need_convert, indent) - rv << res if /\A\s*\z/ !~ res - end - end - rv.join("\n") - end - - def _validate(tags, model=self.class.model) + def _validate(ignore_unknown_element, tags, uri, models=self.class.models) count = 1 do_redo = false not_shift = false tag = nil - element_names = model.collect {|elem| elem[0]} + 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 - model.each_with_index do |elem, i| + models.each_with_index do |model, i| + name, model_uri, occurs, getter = model if DEBUG p "before" p tags - p elem + p model end if not_shift @@ -822,41 +1017,41 @@ EOC p count end - case elem[1] + case occurs when '?' if count > 2 - raise TooMuchTagError.new(elem[0], tag_name) + raise TooMuchTagError.new(name, tag_name) else - if elem[0] == tag + if name == tag do_redo = true else not_shift = true end end when '*' - if elem[0] == tag + if name == tag do_redo = true else not_shift = true end when '+' - if elem[0] == tag + if name == tag do_redo = true else if count > 1 not_shift = true else - raise MissingTagError.new(elem[0], tag_name) + raise MissingTagError.new(name, tag_name) end end else - if elem[0] == tag - if model[i+1] and model[i+1][0] != elem[0] and - tags and tags.first == elem[0] - raise TooMuchTagError.new(elem[0], tag_name) + 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(elem[0], tag_name) + raise MissingTagError.new(name, tag_name) end end @@ -877,8 +1072,8 @@ EOC end - if !tags.nil? and !tags.empty? - raise NotExceptedTagError.new(tag, tag_name) + if !ignore_unknown_element and !tags.nil? and !tags.empty? + raise NotExpectedTagError.new(tags.first, uri, tag_name) end end @@ -892,6 +1087,43 @@ EOC 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 = Base64.encode64(content) if need_base64_encode? + h(content) + end + end + + 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? {|child| child.__send!(:have_required_elements?)} + else + !child.to_s.empty? + end + else + true + end + end + end end module RootElementMixin @@ -899,16 +1131,23 @@ EOC include XMLStyleSheetMixin attr_reader :output_encoding - - def initialize(rss_version, version=nil, encoding=nil, standalone=nil) + attr_reader :feed_type, :feed_subtype, :feed_version + attr_accessor :version, :encoding, :standalone + def initialize(feed_version, version=nil, encoding=nil, standalone=nil) super() - @rss_version = rss_version + @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) @@ -923,14 +1162,48 @@ EOC xss.setup_maker(maker) end - setup_maker_elements(maker) + 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 tag(indent, attrs, &block) - rv = xmldecl + xml_stylesheet_pi - rv << super(indent, attrs, &block) - rv + 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 @@ -944,18 +1217,16 @@ EOC end def ns_declarations + decls = {} self.class::NSPOOL.collect do |prefix, uri| prefix = ":#{prefix}" unless prefix.empty? - ["xmlns#{prefix}", uri] + decls["xmlns#{prefix}"] = uri end + decls end - - def setup_maker_elements(maker) - channel.setup_maker(maker) if channel - image.setup_maker(maker) if image - textinput.setup_maker(maker) if textinput - super(maker) + + def maker_target(target) + target end end - end diff --git a/lib/rss/syndication.rb b/lib/rss/syndication.rb index 8791ec24fc..3eb15429f6 100644 --- a/lib/rss/syndication.rb +++ b/lib/rss/syndication.rb @@ -15,20 +15,26 @@ module RSS def self.append_features(klass) super - - klass.module_eval(<<-EOC, *get_file_and_line_from_caller(1)) + + klass.install_must_call_validator(SY_PREFIX, SY_URI) + klass.module_eval do [ ["updatePeriod"], ["updateFrequency", :positive_integer] ].each do |name, type| - install_text_element("\#{SY_PREFIX}_\#{name}", type, - "\#{SY_PREFIX}:\#{name}") + install_text_element(name, SY_URI, "?", + "#{SY_PREFIX}_#{name}", type, + "#{SY_PREFIX}:#{name}") end %w(updateBase).each do |name| - install_date_element("\#{SY_PREFIX}_\#{name}", 'w3cdtf', 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 @@ -38,20 +44,6 @@ module RSS EOC end - def sy_validate(tags) - counter = {} - ELEMENTS.each do |name| - counter[name] = 0 - end - - tags.each do |tag| - key = "#{SY_PREFIX}_#{tag}" - raise UnknownTagError.new(tag, SY_URI) unless counter.has_key?(key) - counter[key] += 1 - raise TooMuchTagError.new(tag, tag_name) if counter[key] > 1 - end - end - private SY_UPDATEPERIOD_AVAILABLE_VALUES = %w(hourly daily weekly monthly yearly) def validate_sy_updatePeriod(value) @@ -69,7 +61,7 @@ module RSS 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}=") + BaseListener.install_get_text_element(SY_URI, name, full_name) end end diff --git a/lib/rss/taxonomy.rb b/lib/rss/taxonomy.rb index 52267133ff..276f63b05d 100644 --- a/lib/rss/taxonomy.rb +++ b/lib/rss/taxonomy.rb @@ -12,7 +12,7 @@ module RSS %w(link).each do |name| full_name = "#{TAXO_PREFIX}_#{name}" - BaseListener.install_get_text_element(TAXO_URI, name, "#{full_name}=") + BaseListener.install_get_text_element(TAXO_URI, name, full_name) TAXO_ELEMENTS << "#{TAXO_PREFIX}_#{name}" end @@ -28,22 +28,10 @@ module RSS def self.append_features(klass) super - var_name = "#{TAXO_PREFIX}_topics" - klass.install_have_child_element(var_name) - end - - def taxo_validate(tags) - found_topics = false - tags.each do |tag| - if tag == "topics" - if found_topics - raise TooMuchTagError.new(tag, tag_name) - else - found_topics = true - end - else - raise UnknownTagError.new(tag, TAXO_URI) - end + 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 @@ -64,13 +52,17 @@ module RSS @tag_name = "topics" - install_have_child_element("Bag") - - install_must_call_validator('rdf', ::RSS::RDF::URI) + install_have_child_element("Bag", RDF::URI, nil) + install_must_call_validator('rdf', RDF::URI) - def initialize(bag=Bag.new) - super() - @Bag = bag + 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 @@ -80,15 +72,6 @@ module RSS def maker_target(target) target.taxo_topics end - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - Bag_element(need_convert, next_indent), - other_element(need_convert, next_indent), - ] - end - end def resources if @Bag @@ -99,21 +82,6 @@ module RSS [] end end - - private - def children - [@Bag] - end - - def _tags - rv = [] - rv << [::RSS::RDF::URI, 'Bag'] unless @Bag.nil? - rv - end - - def rdf_validate(tags) - _validate(tags, [["Bag", nil]]) - end end end @@ -123,15 +91,7 @@ module RSS def self.append_features(klass) super var_name = "#{TAXO_PREFIX}_topic" - klass.install_have_children_element(var_name) - end - - def taxo_validate(tags) - tags.each do |tag| - if tag != "topic" - raise UnknownTagError.new(tag, TAXO_URI) - end - end + klass.install_have_children_element("topic", TAXO_URI, "*", var_name) end class TaxonomyTopic < Element @@ -152,62 +112,26 @@ module RSS @tag_name = "topic" - install_get_attribute("about", ::RSS::RDF::URI, true) - install_text_element("#{TAXO_PREFIX}_link") + install_get_attribute("about", ::RSS::RDF::URI, true, nil, nil, + "#{RDF::PREFIX}:about") + install_text_element("link", TAXO_URI, "?", "#{TAXO_PREFIX}_link") - def initialize(about=nil) - super() - @about = about + 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 to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) do |next_indent| - [ - other_element(need_convert, next_indent), - ] - end - end - - def taxo_validate(tags) - elements = %w(link topics) - counter = {} - - tags.each do |tag| - if elements.include?(tag) - counter[tag] ||= 0 - counter[tag] += 1 - raise TooMuchTagError.new(tag, tag_name) if counter[tag] > 1 - else - raise UnknownTagError.new(tag, TAXO_URI) - end - end - end def maker_target(target) target.new_taxo_topic end - - private - def children - [@taxo_link, @taxo_topics] - end - - def _attrs - [ - ["#{RDF::PREFIX}:about", true, "about"] - ] - end - - def _tags - rv = [] - rv << [TAXO_URI, "link"] unless @taxo_link.nil? - rv << [TAXO_URI, "topics"] unless @taxo_topics.nil? - rv - end end end diff --git a/lib/rss/trackback.rb b/lib/rss/trackback.rb index ad6954f3f5..ee2491f332 100644 --- a/lib/rss/trackback.rb +++ b/lib/rss/trackback.rb @@ -11,28 +11,15 @@ module RSS module TrackBackUtils private - def trackback_validate(tags) - counter = {} - %w(ping about).each do |name| - counter["#{TRACKBACK_PREFIX}_#{name}"] = 0 - end - - tags.each do |tag| - key = "#{TRACKBACK_PREFIX}_#{tag}" - raise UnknownTagError.new(tag, TRACKBACK_URI) unless counter.has_key?(key) - counter[key] += 1 - if tag != "about" and counter[key] > 1 - raise TooMuchTagError.new(tag, tag_name) - end - end - - if counter["#{TRACKBACK_PREFIX}_ping"].zero? and - counter["#{TRACKBACK_PREFIX}_about"].nonzero? + 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) @@ -43,10 +30,11 @@ module RSS 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(var_name) + klass.install_have_child_element(name, TRACKBACK_URI, "?", var_name) klass.module_eval(<<-EOC, __FILE__, __LINE__) remove_method :#{var_name} def #{var_name} @@ -63,7 +51,8 @@ module RSS [%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(var_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) @@ -124,38 +113,28 @@ module RSS end @tag_name = "ping" - + [ ["resource", ::RSS::RDF::URI, true] ].each do |name, uri, required| - install_get_attribute(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(resource=nil) - super() - @resource = 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 - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) - rv = convert(rv) if need_convert - rv - end - - private - def _attrs - [ - ["#{::RSS::RDF::PREFIX}:resource", true, "resource"], - ] - end - end class TrackBackAbout < Element @@ -174,38 +153,31 @@ module RSS end @tag_name = "about" - + [ ["resource", ::RSS::RDF::URI, true] ].each do |name, uri, required| - install_get_attribute(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(resource=nil) - super() - @resource = 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 - - def to_s(need_convert=true, indent=calc_indent) - rv = tag(indent) - rv = convert(rv) if need_convert - rv - end private - def _attrs - [ - ["#{::RSS::RDF::PREFIX}:resource", true, "resource"], - ] - end - def maker_target(abouts) abouts.new_about end @@ -243,9 +215,13 @@ module RSS alias_method(:value, :content) alias_method(:value=, :content=) - def initialize(content=nil) - super() - @content = content + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.content = args[0] + end end def full_name @@ -276,9 +252,13 @@ module RSS alias_method(:value, :content) alias_method(:value=, :content=) - def initialize(content=nil) - super() - @content = content + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.content = args[0] + end end def full_name diff --git a/lib/rss/utils.rb b/lib/rss/utils.rb index 0286becf00..031ff3072b 100644 --- a/lib/rss/utils.rb +++ b/lib/rss/utils.rb @@ -1,7 +1,5 @@ module RSS - module Utils - module_function def to_class_name(name) name.split(/_/).collect do |part| @@ -26,6 +24,9 @@ module RSS klass.new(value) end end - end + def element_initialize_arguments?(args) + [true, false].include?(args[0]) and args[1].is_a?(Hash) + end + end end diff --git a/lib/rss/xml.rb b/lib/rss/xml.rb new file mode 100644 index 0000000000..1ae878b772 --- /dev/null +++ b/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 |