diff --git a/ChangeLog b/ChangeLog index c36a9e7af2..1d0f0918aa 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +Fri Feb 4 00:24:15 2005 Kouhei Sutou + + * lib/rss: supported Image module. + http://web.resource.org/rss/1.0/modules/image/ + Thu Feb 3 23:42:36 2005 Nobuyoshi Nakada * ext/stringio/stringio.c (strio_close, strio_close_read, strio_close_write): diff --git a/lib/rss/0.9.rb b/lib/rss/0.9.rb index 24560324ed..2bb59763d9 100644 --- a/lib/rss/0.9.rb +++ b/lib/rss/0.9.rb @@ -223,6 +223,10 @@ module RSS end end + def not_need_to_call_setup_maker_variables + %w(image textInput) + end + class SkipDays < Element include RSS09 diff --git a/lib/rss/dublincore.rb b/lib/rss/dublincore.rb index 7ea9cf31dd..afdd4d392d 100644 --- a/lib/rss/dublincore.rb +++ b/lib/rss/dublincore.rb @@ -56,7 +56,6 @@ module RSS end prefix_size = DC_PREFIX.size + 1 - DublinCoreModel::ELEMENTS.uniq! DublinCoreModel::ELEMENTS.each do |x| BaseListener.install_get_text_element(x[prefix_size..-1], DC_URI, "#{x}=") end diff --git a/lib/rss/image.rb b/lib/rss/image.rb new file mode 100644 index 0000000000..9cc3c73018 --- /dev/null +++ b/lib/rss/image.rb @@ -0,0 +1,216 @@ +require 'rss/1.0' +require 'rss/dublincore' + +module RSS + + IMAGE_PREFIX = 'image' + IMAGE_URI = 'http://web.resource.org/rss/1.0/modules/image/' + + RDF.install_ns(IMAGE_PREFIX, IMAGE_URI) + + module ImageModelUtils + def validate_one_tag_name(name, tags) + invalid = tags.find {|tag| tag != name} + raise UnknownTagError.new(invalid, IMAGE_URI) if invalid + raise TooMuchTagError.new(name, tag_name) if tags.size > 1 + end + end + + module ImageItemModel + include ImageModelUtils + extend BaseModel + + def self.append_features(klass) + super + + klass.install_have_child_element("#{IMAGE_PREFIX}_item") + end + + def image_validate(tags) + validate_one_tag_name("item", tags) + end + + class Item < Element + include RSS10 + include DublinCoreModel + + class << self + def required_prefix + IMAGE_PREFIX + end + + def required_uri + IMAGE_URI + end + end + + [ + ["about", ::RSS::RDF::URI, true], + ["resource", ::RSS::RDF::URI, false], + ].each do |name, uri, required| + install_get_attribute(name, uri, required) + end + + %w(width height).each do |tag| + full_name = "#{IMAGE_PREFIX}_#{tag}" + install_text_element(full_name) + BaseListener.install_get_text_element(tag, IMAGE_URI, "#{full_name}=") + end + + def initialize(about=nil, resource=nil) + super() + @about = about + @resource = resource + 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 + + alias _image_width= image_width= + def image_width=(new_value) + if @do_validate + self._image_width = Integer(new_value) + else + self._image_width = new_value.to_i + end + end + + alias _image_height= image_height= + def image_height=(new_value) + if @do_validate + self._image_height = Integer(new_value) + else + self._image_height = new_value.to_i + end + end + + alias width= image_width= + alias width image_width + alias height= image_height= + alias height image_height + + private + def _tags + [ + [IMAGE_URI, 'width'], + [IMAGE_URI, 'height'], + ].delete_if do |x| + send(x[1]).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 + + def setup_maker_attributes(item) + item.about = self.about + item.resource = self.resource + end + end + end + + module ImageFaviconModel + include ImageModelUtils + extend BaseModel + + def self.append_features(klass) + super + + unless klass.class == Module + klass.install_have_child_element("#{IMAGE_PREFIX}_favicon") + end + end + + def image_validate(tags) + validate_one_tag_name("favicon", tags) + end + + class Favicon < Element + include RSS10 + include DublinCoreModel + + class << self + def required_prefix + IMAGE_PREFIX + end + + def required_uri + IMAGE_URI + end + end + + [ + ["about", ::RSS::RDF::URI, true], + ["size", IMAGE_URI, true], + ].each do |name, uri, required| + install_get_attribute(name, uri, required) + end + + alias image_size= size= + alias image_size size + + def initialize(about=nil, size=nil) + super() + @about = about + @size = size + 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 + + def setup_maker_attributes(favicon) + favicon.about = self.about + favicon.size = self.size + end + end + + end + + class RDF + class Channel; include ImageFaviconModel; end + class Item; include ImageItemModel; end + end + +end diff --git a/lib/rss/maker.rb b/lib/rss/maker.rb index 66c3bc2928..d248711aa2 100644 --- a/lib/rss/maker.rb +++ b/lib/rss/maker.rb @@ -33,3 +33,4 @@ require "rss/maker/content" require "rss/maker/dublincore" require "rss/maker/syndication" require "rss/maker/trackback" +require "rss/maker/image" diff --git a/lib/rss/maker/base.rb b/lib/rss/maker/base.rb index 7faafe8d4e..0b157b1ee2 100644 --- a/lib/rss/maker/base.rb +++ b/lib/rss/maker/base.rb @@ -144,7 +144,7 @@ module RSS end def make_#{element} - self.class::#{element[0,1].upcase}#{element[1..-1]}.new(self) + self.class::#{Utils.to_class_name(element)}.new(self) end EOC end @@ -255,7 +255,7 @@ EOC end def make_#{element} - self.class::#{element[0,1].upcase}#{element[1..-1]}.new(@maker) + self.class::#{Utils.to_class_name(element)}.new(@maker) end EOC end @@ -443,7 +443,7 @@ EOC end def make_#{element} - self.class::#{element[0,1].upcase}#{element[1..-1]}.new(@maker) + self.class::#{Utils.to_class_name(element)}.new(@maker) end EOC end diff --git a/lib/rss/maker/dublincore.rb b/lib/rss/maker/dublincore.rb index a9b8d58715..3eeb269df1 100644 --- a/lib/rss/maker/dublincore.rb +++ b/lib/rss/maker/dublincore.rb @@ -7,7 +7,7 @@ module RSS def self.append_features(klass) super - ::RSS::DublinCoreModel::ELEMENTS.each do |element| + ::RSS::DublinCoreModel::ELEMENTS.uniq.each do |element| klass.add_need_initialize_variable(element) klass.add_other_element(element) klass.__send__(:attr_accessor, element) diff --git a/lib/rss/maker/image.rb b/lib/rss/maker/image.rb new file mode 100644 index 0000000000..98d59f733c --- /dev/null +++ b/lib/rss/maker/image.rb @@ -0,0 +1,136 @@ +require 'rss/image' +require 'rss/maker/1.0' +require 'rss/maker/dublincore' + +module RSS + module Maker + module ImageItemModel + def self.append_features(klass) + super + + name = "#{RSS::IMAGE_PREFIX}_item" + klass.add_need_initialize_variable(name, "make_#{name}") + klass.add_other_element(name) + klass.__send__(:attr_reader, name) + klass.module_eval(<<-EOC, __FILE__, __LINE__) + def setup_#{name}(rss, current) + if @#{name} + @#{name}.to_rss(rss, current) + end + end + + def make_#{name} + self.class::#{Utils.to_class_name(name)}.new(@maker) + end +EOC + end + + class ImageItemBase + include Base + include Maker::DublinCoreModel + + attr_accessor :about, :resource, :image_width, :image_height + add_need_initialize_variable(:about, :resource) + add_need_initialize_variable(:image_width, :image_height) + alias width= image_width= + alias width image_width + alias height= image_height= + alias height image_height + + def have_required_values? + @about + end + end + end + + module ImageFaviconModel + def self.append_features(klass) + super + + name = "#{RSS::IMAGE_PREFIX}_favicon" + klass.add_need_initialize_variable(name, "make_#{name}") + klass.add_other_element(name) + klass.__send__(:attr_reader, name) + klass.module_eval(<<-EOC, __FILE__, __LINE__) + def setup_#{name}(rss, current) + if @#{name} + @#{name}.to_rss(rss, current) + end + end + + def make_#{name} + self.class::#{Utils.to_class_name(name)}.new(@maker) + end +EOC + end + + class ImageFaviconBase + include Base + include Maker::DublinCoreModel + + attr_accessor :about, :image_size + add_need_initialize_variable(:about, :image_size) + alias size image_size + alias size= image_size= + + def have_required_values? + @about and @image_size + end + end + end + + class ChannelBase; include Maker::ImageFaviconModel; end + + class ItemsBase + class ItemBase; include Maker::ImageItemModel; end + end + + class RSS10 + class Items + class Item + class ImageItem < ImageItemBase + def to_rss(rss, current) + if @about + item = ::RSS::ImageItemModel::Item.new(@about, @resource) + setup_values(item) + current.image_item = item + end + end + end + end + end + + class Channel + class ImageFavicon < ImageFaviconBase + def to_rss(rss, current) + if @about and @image_size + args = [@about, @image_size] + favicon = ::RSS::ImageFaviconModel::Favicon.new(*args) + setup_values(favicon) + current.image_favicon = favicon + end + end + end + end + end + + class RSS09 + class Items + class Item + class ImageItem < ImageItemBase + def to_rss(*args) + end + end + end + end + + class Channel + class ImageFavicon < ImageFaviconBase + def to_rss(*args) + end + end + end + end + + end +end diff --git a/lib/rss/rss.rb b/lib/rss/rss.rb index 7ffb475007..848fbd7b79 100644 --- a/lib/rss/rss.rb +++ b/lib/rss/rss.rb @@ -627,12 +627,22 @@ EOC def setup_maker_element(target) self.class.need_initialize_variables.each do |var| - setter = "#{var}=" - if target.respond_to?(setter) - target.__send__(setter, __send__(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| diff --git a/lib/rss/utils.rb b/lib/rss/utils.rb index 1c30ae74bc..19f27183eb 100644 --- a/lib/rss/utils.rb +++ b/lib/rss/utils.rb @@ -2,6 +2,13 @@ module RSS module Utils + module_function + def to_class_name(name) + name.split(/_/).collect do |part| + "#{part[0, 1].upcase}#{part[1..-1]}" + end.join("") + end + def get_file_and_line_from_caller(i=0) file, line, = caller[i].split(':') [file, line.to_i] diff --git a/test/rss/rss-testcase.rb b/test/rss/rss-testcase.rb index 27b4eba5d6..f3a2760fd2 100644 --- a/test/rss/rss-testcase.rb +++ b/test/rss/rss-testcase.rb @@ -1,10 +1,11 @@ -# -*- tab-width: 2 -*- vim: ts=2 +require "erb" require "test/unit" require 'rss-assertions' module RSS class TestCase < Test::Unit::TestCase + include ERB::Util include RSS include Assertions @@ -189,6 +190,17 @@ EOI EOC end + def make_element(elem_name, attrs, contents) + attrs_str = attrs.collect do |name, value| + "#{h name}='#{h value}'" + end.join(" ") + contents_str = contents.collect do |name, value| + "#{Element::INDENT}<#{h name}>#{h value}" + end.join("\n") + + "<#{h elem_name} #{attrs_str}>\n#{contents_str}\n" + end + private def setup_dummy_channel(maker) about = "http://hoge.com" diff --git a/test/rss/test_image.rb b/test/rss/test_image.rb new file mode 100644 index 0000000000..8e62085b43 --- /dev/null +++ b/test/rss/test_image.rb @@ -0,0 +1,165 @@ +require "cgi" +require "rexml/document" + +require "rss-testcase" + +require "rss/1.0" +require "rss/image" + +module RSS + class TestImage < TestCase + + def setup + @prefix = "image" + @uri = "http://web.resource.org/rss/1.0/modules/image/" + + @favicon_attrs = { + "rdf:about" => "http://www.kuro5hin.org/favicon.ico", + "#{@prefix}:size" => "small", + } + @favicon_contents = {"dc:title" => "Kuro5hin",} + @items = [ + [ + { + "rdf:about" => "http://www.example.org/item.png", + "rdf:resource" => "http://www.example.org/item", + }, + { + "dc:title" => "Example Image", + "#{@prefix}:width" => 100, + "#{@prefix}:height" => 65, + }, + ], + [ + { + "rdf:about" => "http://www.kuro5hin.org/images/topics/culture.jpg", + }, + { + "dc:title" => "Culture", + "#{@prefix}:width" => 80, + "#{@prefix}:height" => 50, + }, + ] + ] + + + @channel_nodes = make_element("#{@prefix}:favicon", + @favicon_attrs, + @favicon_contents) + items = "" + @items.each do |attrs, contents| + image_item = make_element("#{@prefix}:item", attrs, contents) + items << make_item(image_item) + end + + ns = { + @prefix => @uri, + DC_PREFIX => DC_URI, + } + @rss_source = make_RDF(<<-EOR, ns) +#{make_channel(@channel_nodes)} +#{make_image} +#{items} +#{make_textinput} +EOR + + @rss = Parser.parse(@rss_source) + end + + def test_parser + assert_nothing_raised do + Parser.parse(@rss_source) + end + + assert_too_much_tag("favicon", "channel") do + Parser.parse(make_RDF(<<-EOR, {@prefix => @uri})) +#{make_channel(@channel_nodes * 2)} +#{make_item} +EOR + end + end + + def test_favicon_accessor + favicon = @rss.channel.image_favicon + [ + %w(about rdf:about http://example.com/favicon.ico), + %w(size image:size large), + %w(image_size image:size medium), + ].each do |name, full_name, new_value| + assert_equal(@favicon_attrs[full_name], favicon.send(name)) + favicon.send("#{name}=", new_value) + assert_equal(new_value, favicon.send(name)) + favicon.send("#{name}=", @favicon_attrs[full_name]) + assert_equal(@favicon_attrs[full_name], favicon.send(name)) + end + + [ + %w(dc_title dc:title sample-favicon), + ].each do |name, full_name, new_value| + assert_equal(@favicon_contents[full_name], favicon.send(name)) + favicon.send("#{name}=", new_value) + assert_equal(new_value, favicon.send(name)) + favicon.send("#{name}=", @favicon_contents[full_name]) + assert_equal(@favicon_contents[full_name], favicon.send(name)) + end + end + + def test_item_accessor + @rss.items.each_with_index do |item, i| + image_item = item.image_item + attrs, contents = @items[i] + [ + %w(about rdf:about http://example.com/image.png), + %w(resource rdf:resource http://example.com/), + ].each do |name, full_name, new_value| + assert_equal(attrs[full_name], image_item.send(name)) + image_item.send("#{name}=", new_value) + assert_equal(new_value, image_item.send(name)) + image_item.send("#{name}=", attrs[full_name]) + assert_equal(attrs[full_name], image_item.send(name)) + end + + [ + ["width", "image:width", 111], + ["image_width", "image:width", 44], + ["height", "image:height", 222], + ["image_height", "image:height", 88], + ["dc_title", "dc:title", "sample-image"], + ].each do |name, full_name, new_value| + assert_equal(contents[full_name], image_item.send(name)) + image_item.send("#{name}=", new_value) + assert_equal(new_value, image_item.send(name)) + image_item.send("#{name}=", contents[full_name]) + assert_equal(contents[full_name], image_item.send(name)) + end + end + end + + def test_favicon_to_s + favicon = @rss.channel.image_favicon + expected = REXML::Document.new(make_element("#{@prefix}:favicon", + @favicon_attrs, + @favicon_contents)) + actual = REXML::Document.new(favicon.to_s(false, "")) + assert_equal(expected.to_s, actual.to_s) + end + + def test_item_to_s + @rss.items.each_with_index do |item, i| + attrs, contents = @items[i] + expected_s = make_element("#{@prefix}:item", attrs, contents) + expected = REXML::Document.new(expected_s) + actual = REXML::Document.new(item.image_item.to_s(false, "")) + + assert_equal(expected[0].attributes, actual[0].attributes) + + %w(image:height image:width dc:title).each do |name| + actual_target = actual.elements["//#{name}"] + expected_target = expected.elements["//#{name}"] + assert_equal(expected_target.to_s, actual_target.to_s) + end + end + end + + end +end diff --git a/test/rss/test_maker_image.rb b/test/rss/test_maker_image.rb new file mode 100644 index 0000000000..1c161b2593 --- /dev/null +++ b/test/rss/test_maker_image.rb @@ -0,0 +1,62 @@ +require "rss-testcase" + +require "rss/maker" + +module RSS + class TestMakerImage < TestCase + + def setup + @uri = "http://web.resource.org/rss/1.0/modules/image/" + + @favicon_infos = { + "about" => "http://www.kuro5hin.org/favicon.ico", + "image_size" => "small", + "dc_title" => "example", + } + @item_infos = { + "about" => "http://www.example.org/item.png", + "resource" => "http://www.example.org/item", + "dc_title" => "Example Image", + "image_width" => 100, + "image_height" => 65, + } + end + + def test_rss10 + rss = RSS::Maker.make("1.0") do |maker| + setup_dummy_channel(maker) + @favicon_infos.each do |name, value| + maker.channel.image_favicon.__send__("#{name}=", value) + end + + setup_dummy_image(maker) + + setup_dummy_item(maker) + item = maker.items.last + @item_infos.each do |name, value| + item.image_item.__send__("#{name}=", value) + end + + setup_dummy_textinput(maker) + end + + setup_rss = RSS::Maker.make("1.0") do |maker| + rss.setup_maker(maker) + end + + [rss, setup_rss].each_with_index do |target, i| + favicon = target.channel.image_favicon + assert_equal(@favicon_infos["about"], favicon.about) + assert_equal(@favicon_infos["image_size"], favicon.image_size) + assert_equal(@favicon_infos["dc_title"], favicon.dc_title) + + item = target.items.last.image_item + assert_equal(@item_infos["about"], item.about) + assert_equal(@item_infos["resource"], item.resource) + assert_equal(@item_infos["image_width"], item.image_width) + assert_equal(@item_infos["image_height"], item.image_height) + assert_equal(@item_infos["dc_title"], item.dc_title) + end + end + end +end