diff options
81 files changed, 11748 insertions, 1613 deletions
@@ -1,3 +1,14 @@ +Sun Oct 21 21:16:43 2007 Kouhei Sutou <kou@cozmixng.org> + + * lib/rss.rb, lib/rss/, test/rss/, sample/rss/: merged from trunk. + - 0.1.6 -> 2.0.0. + - fixed image module URI. Thanks to Dmitry Borodaenko. + - supported Atom. + - supported ITunes module. + - supported Slash module. + + * NEWS: added an entry for RSS Parser. + Thu Oct 18 10:57:06 2007 Tanaka Akira <akr@fsij.org> * ruby.h (RCLASS_IV_TBL): defined. diff --git a/lib/rss.rb b/lib/rss.rb index 495edb1b98..a1d0f76ba1 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-2007 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,14 @@ require 'rss/1.0' require 'rss/2.0' +require 'rss/atom' require 'rss/content' require 'rss/dublincore' require 'rss/image' +require 'rss/itunes' +require 'rss/slash' require 'rss/syndication' -#require 'rss/taxonomy' +require 'rss/taxonomy' require 'rss/trackback' require "rss/maker" diff --git a/lib/rss/.gdbinit b/lib/rss/.gdbinit new file mode 100644 index 0000000000..5f67324456 --- /dev/null +++ b/lib/rss/.gdbinit @@ -0,0 +1,499 @@ +define rp + if (VALUE)$arg0 & RUBY_FIXNUM_FLAG + printf "FIXNUM: %d\n", $arg0 >> 1 + else + if ((VALUE)$arg0 & ~(~(VALUE)0<<RUBY_SPECIAL_SHIFT)) == RUBY_SYMBOL_FLAG + printf "SYMBOL(%d)\n", $arg0 >> RUBY_SPECIAL_SHIFT + else + if $arg0 == RUBY_Qfalse + echo false\n + else + if $arg0 == RUBY_Qtrue + echo true\n + else + if $arg0 == RUBY_Qnil + echo nil\n + else + if $arg0 == RUBY_Qundef + echo undef\n + else + if (VALUE)$arg0 & RUBY_IMMEDIATE_MASK + echo immediate\n + else + set $flags = ((struct RBasic*)$arg0)->flags + if ($flags & RUBY_T_MASK) == RUBY_T_NONE + printf "T_NONE: " + print (struct RBasic *)$arg0 + else + if ($flags & RUBY_T_MASK) == RUBY_T_NIL + printf "T_NIL: " + print (struct RBasic *)$arg0 + else + if ($flags & RUBY_T_MASK) == RUBY_T_OBJECT + printf "T_OBJECT: " + print (struct RObject *)$arg0 + else + if ($flags & RUBY_T_MASK) == RUBY_T_CLASS + printf "T_CLASS: " + print (struct RClass *)$arg0 + else + if ($flags & RUBY_T_MASK) == RUBY_T_ICLASS + printf "T_ICLASS: " + print (struct RClass *)$arg0 + else + if ($flags & RUBY_T_MASK) == RUBY_T_MODULE + printf "T_MODULE: " + print (struct RClass *)$arg0 + else + if ($flags & RUBY_T_MASK) == RUBY_T_FLOAT + printf "T_FLOAT: %.16g ", (((struct RFloat*)$arg0)->value) + print (struct RFloat *)$arg0 + else + if ($flags & RUBY_T_MASK) == RUBY_T_STRING + printf "T_STRING: " + set print address off + output (char *)(($flags & RUBY_FL_USER1) ? \ + ((struct RString*)$arg0)->as.heap.ptr : \ + ((struct RString*)$arg0)->as.ary) + set print address on + printf " encoding:%d ", ($flags & (RUBY_FL_USER8|RUBY_FL_USER9|RUBY_FL_USER10|RUBY_FL_USER11)) >> RUBY_ENCODING_SHIFT + if ($flags & (RUBY_FL_USER12|RUBY_FL_USER13)) == 0 + printf "coderange:unknown " + else + if ($flags & (RUBY_FL_USER12|RUBY_FL_USER13)) == RUBY_FL_USER12 + printf "coderange:single " + else + if ($flags & (RUBY_FL_USER12|RUBY_FL_USER13)) == RUBY_FL_USER13 + printf "coderange:single " + else + printf "coderange:broken " + end + end + end + print (struct RString *)$arg0 + else + if ($flags & RUBY_T_MASK) == RUBY_T_REGEXP + printf "T_REGEXP: " + set print address off + output ((struct RRegexp*)$arg0)->str + set print address on + printf " " + print (struct RRegexp *)$arg0 + else + if ($flags & RUBY_T_MASK) == RUBY_T_ARRAY + printf "T_ARRAY: len=%d ", ((struct RArray*)$arg0)->len + print (struct RArray *)$arg0 + x/xw ((struct RArray*)$arg0)->ptr + else + if ($flags & RUBY_T_MASK) == RUBY_T_FIXNUM + printf "T_FIXNUM: " + print (struct RBasic *)$arg0 + else + if ($flags & RUBY_T_MASK) == RUBY_T_HASH + printf "T_HASH: ", + if ((struct RHash *)$arg0)->ntbl + printf "len=%d ", ((struct RHash *)$arg0)->ntbl->num_entries + end + print (struct RHash *)$arg0 + else + if ($flags & RUBY_T_MASK) == RUBY_T_STRUCT + printf "T_STRUCT: len=%d ", \ + (($flags & (RUBY_FL_USER1|RUBY_FL_USER2)) ? \ + ($flags & (RUBY_FL_USER1|RUBY_FL_USER2)) >> (RUBY_FL_USHIFT+1) : \ + ((struct RStruct *)$arg0)->as.heap.len) + print (struct RStruct *)$arg0 + x/xw (($flags & (RUBY_FL_USER1|RUBY_FL_USER2)) ? \ + ((struct RStruct *)$arg0)->as.ary : \ + ((struct RStruct *)$arg0)->as.heap.len) + else + if ($flags & RUBY_T_MASK) == RUBY_T_BIGNUM + printf "T_BIGNUM: sign=%d len=%d ", \ + (($flags & RUBY_FL_USER1) != 0), \ + (($flags & RUBY_FL_USER2) ? \ + ($flags & (RUBY_FL_USER5|RUBY_FL_USER4|RUBY_FL_USER3)) >> (RUBY_FL_USHIFT+3) : \ + ((struct RBignum*)$arg0)->as.heap.len) + if $flags & RUBY_FL_USER2 + printf "(embed) " + end + print (struct RBignum *)$arg0 + x/xw (($flags & RUBY_FL_USER2) ? \ + ((struct RBignum*)$arg0)->as.ary : \ + ((struct RBignum*)$arg0)->as.heap.digits) + else + if ($flags & RUBY_T_MASK) == RUBY_T_FILE + printf "T_FILE: " + print (struct RFile *)$arg0 + output *((struct RFile *)$arg0)->fptr + printf "\n" + else + if ($flags & RUBY_T_MASK) == RUBY_T_TRUE + printf "T_TRUE: " + print (struct RBasic *)$arg0 + else + if ($flags & RUBY_T_MASK) == RUBY_T_FALSE + printf "T_FALSE: " + print (struct RBasic *)$arg0 + else + if ($flags & RUBY_T_MASK) == RUBY_T_DATA + printf "T_DATA: " + print (struct RData *)$arg0 + else + if ($flags & RUBY_T_MASK) == RUBY_T_MATCH + printf "T_MATCH: " + print (struct RMatch *)$arg0 + else + if ($flags & RUBY_T_MASK) == RUBY_T_SYMBOL + printf "T_SYMBOL: " + print (struct RBasic *)$arg0 + else + if ($flags & RUBY_T_MASK) == RUBY_T_VALUES + printf "T_VALUES: " + print (struct RValues *)$arg0 + else + if ($flags & RUBY_T_MASK) == RUBY_T_BLOCK + printf "T_BLOCK: " + print (struct RBasic *)$arg0 + else + if ($flags & RUBY_T_MASK) == RUBY_T_UNDEF + printf "T_UNDEF: " + print (struct RBasic *)$arg0 + else + if ($flags & RUBY_T_MASK) == RUBY_T_NODE + printf "T_NODE(" + output (enum node_type)(($flags&RUBY_NODE_TYPEMASK)>>RUBY_NODE_TYPESHIFT) + printf "): " + print *(NODE *)$arg0 + else + printf "unknown: " + print (struct RBasic *)$arg0 + end + end + end + end + end + end + end + end + end + end + end + end + end + end + end + end + end + end + end + end + end + end + end + end + end + end + end + end + end + end + end +end +document rp + Print a Ruby's VALUE. +end + +define nd_type + print (enum node_type)((((NODE*)$arg0)->flags&RUBY_NODE_TYPEMASK)>>RUBY_NODE_TYPESHIFT) +end +document nd_type + Print a Ruby' node type. +end + +define nd_file + print ((NODE*)$arg0)->nd_file +end +document nd_file + Print the source file name of a node. +end + +define nd_line + print ((unsigned int)((((NODE*)$arg0)->flags>>RUBY_NODE_LSHIFT)&RUBY_NODE_LMASK)) +end +document nd_line + Print the source line number of a node. +end + +# Print members of ruby node. + +define nd_head + printf "u1.node: " + rp $arg0.u1.node +end + +define nd_alen + printf "u2.argc: " + p $arg0.u2.argc +end + +define nd_next + printf "u3.node: " + rp $arg0.u3.node +end + + +define nd_cond + printf "u1.node: " + rp $arg0.u1.node +end + +define nd_body + printf "u2.node: " + rp $arg0.u2.node +end + +define nd_else + printf "u3.node: " + rp $arg0.u3.node +end + + +define nd_orig + printf "u3.value: " + rp $arg0.u3.value +end + + +define nd_resq + printf "u2.node: " + rp $arg0.u2.node +end + +define nd_ensr + printf "u3.node: " + rp $arg0.u3.node +end + + +define nd_1st + printf "u1.node: " + rp $arg0.u1.node +end + +define nd_2nd + printf "u2.node: " + rp $arg0.u2.node +end + + +define nd_stts + printf "u1.node: " + rp $arg0.u1.node +end + + +define nd_entry + printf "u3.entry: " + p $arg0.u3.entry +end + +define nd_vid + printf "u1.id: " + p $arg0.u1.id +end + +define nd_cflag + printf "u2.id: " + p $arg0.u2.id +end + +define nd_cval + printf "u3.value: " + rp $arg0.u3.value +end + + +define nd_cnt + printf "u3.cnt: " + p $arg0.u3.cnt +end + +define nd_tbl + printf "u1.tbl: " + p $arg0.u1.tbl +end + + +define nd_var + printf "u1.node: " + rp $arg0.u1.node +end + +define nd_ibdy + printf "u2.node: " + rp $arg0.u2.node +end + +define nd_iter + printf "u3.node: " + rp $arg0.u3.node +end + + +define nd_value + printf "u2.node: " + rp $arg0.u2.node +end + +define nd_aid + printf "u3.id: " + p $arg0.u3.id +end + + +define nd_lit + printf "u1.value: " + rp $arg0.u1.value +end + + +define nd_frml + printf "u1.node: " + rp $arg0.u1.node +end + +define nd_rest + printf "u2.argc: " + p $arg0.u2.argc +end + +define nd_opt + printf "u1.node: " + rp $arg0.u1.node +end + + +define nd_recv + printf "u1.node: " + rp $arg0.u1.node +end + +define nd_mid + printf "u2.id: " + p $arg0.u2.id +end + +define nd_args + printf "u3.node: " + rp $arg0.u3.node +end + + +define nd_noex + printf "u1.id: " + p $arg0.u1.id +end + +define nd_defn + printf "u3.node: " + rp $arg0.u3.node +end + + +define nd_old + printf "u1.id: " + p $arg0.u1.id +end + +define nd_new + printf "u2.id: " + p $arg0.u2.id +end + + +define nd_cfnc + printf "u1.cfunc: " + p $arg0.u1.cfunc +end + +define nd_argc + printf "u2.argc: " + p $arg0.u2.argc +end + + +define nd_cname + printf "u1.id: " + p $arg0.u1.id +end + +define nd_super + printf "u3.node: " + rp $arg0.u3.node +end + + +define nd_modl + printf "u1.id: " + p $arg0.u1.id +end + +define nd_clss + printf "u1.value: " + rp $arg0.u1.value +end + + +define nd_beg + printf "u1.node: " + rp $arg0.u1.node +end + +define nd_end + printf "u2.node: " + rp $arg0.u2.node +end + +define nd_state + printf "u3.state: " + p $arg0.u3.state +end + +define nd_rval + printf "u2.value: " + rp $arg0.u2.value +end + + +define nd_nth + printf "u2.argc: " + p $arg0.u2.argc +end + + +define nd_tag + printf "u1.id: " + p $arg0.u1.id +end + +define nd_tval + printf "u2.value: " + rp $arg0.u2.value +end + +define rb_p + call rb_p($arg0) +end + +define rb_id2name + call rb_id2name($arg0) +end + +define rb_classname + call classname($arg0) + rb_p $ + print *(struct RClass*)$arg0 +end + +define rb_backtrace + call rb_backtrace() +end diff --git a/lib/rss/0.9.rb b/lib/rss/0.9.rb index 69e01ddd57..7b24e7596d 100644 --- a/lib/rss/0.9.rb +++ b/lib/rss/0.9.rb @@ -22,10 +22,13 @@ module RSS 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 @@ -57,12 +60,14 @@ module RSS 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 @@ -400,21 +405,22 @@ module RSS end RSS09::ELEMENTS.each do |name| - BaseListener.install_get_text_element("", name, "#{name}=") + BaseListener.install_get_text_element("", name, name) end module ListenerMixin private - def start_rss(tag_name, prefix, attrs, ns) + 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| + 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 a945434fbf..f04e61c5eb 100644 --- a/lib/rss/1.0.rb +++ b/lib/rss/1.0.rb @@ -45,10 +45,10 @@ module RSS __send__("install_have_#{type}_element", tag, ::RSS::URI, occurs) end - 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 @@ -430,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| + 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 44bdb4f1ae..3798da4eb7 100644 --- a/lib/rss/2.0.rb +++ b/lib/rss/2.0.rb @@ -105,7 +105,7 @@ module RSS end RSS09::ELEMENTS.each do |name| - BaseListener.install_get_text_element("", 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..7cba934feb --- /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) {|x| x} + end + + def setup_maker_attributes(target) + target.type = type + target.content = content + target.xml_content = @xhtml + end + end + + module PersonConstruct + def self.append_features(klass) + super + klass.class_eval do + [ + ["name", nil], + ["uri", "?"], + ["email", "?"], + ].each do |tag, occurs| + install_have_attribute_element(tag, URI, occurs, nil, :content) + end + end + end + + def maker_target(target) + target.__send__("new_#{self.class.name.split(/::/).last.downcase}") + end + + class Name < RSS::Element + include CommonModel + include ContentModel + end + + class Uri < RSS::Element + include CommonModel + include URIContentModel + end + + class Email < RSS::Element + include CommonModel + include ContentModel + end + end + + module DateConstruct + def self.append_features(klass) + super + klass.class_eval do + @content_type = :w3cdtf + include(ContentModel) + end + end + + def atom_validate(ignore_unknown_element, tags, uri) + raise NotAvailableValueError.new(tag_name, "") if content.nil? + end + end + + module DuplicateLinkChecker + def validate_duplicate_links(links) + link_infos = {} + links.each do |link| + rel = link.rel || "alternate" + next unless rel == "alternate" + key = [link.hreflang, link.type] + if link_infos.has_key?(key) + raise TooMuchTagError.new("link", tag_name) + end + link_infos[key] = true + end + end + end + + class Feed < RSS::Element + include RootElementMixin + include CommonModel + include DuplicateLinkChecker + + install_ns('', URI) + + [ + ["author", "*", :children], + ["category", "*", :children, "categories"], + ["contributor", "*", :children], + ["generator", "?"], + ["icon", "?", nil, :content], + ["id", nil, nil, :content], + ["link", "*", :children], + ["logo", "?"], + ["rights", "?"], + ["subtitle", "?", nil, :content], + ["title", nil, nil, :content], + ["updated", nil, nil, :content], + ["entry", "*", :children, "entries"], + ].each do |tag, occurs, type, *args| + type ||= :child + __send__("install_have_#{type}_element", + tag, URI, occurs, tag, *args) + end + + def initialize(version=nil, encoding=nil, standalone=nil) + super("1.0", version, encoding, standalone) + @feed_type = "atom" + @feed_subtype = "feed" + end + + alias_method :items, :entries + + def have_author? + authors.any? {|author| !author.to_s.empty?} or + entries.any? {|entry| entry.have_author?(false)} + end + + private + def atom_validate(ignore_unknown_element, tags, uri) + unless have_author? + raise MissingTagError.new("author", tag_name) + end + validate_duplicate_links(links) + end + + def have_required_elements? + super and have_author? + end + + def maker_target(maker) + maker.channel + end + + def setup_maker_element(channel) + prev_dc_dates = channel.dc_dates.to_a.dup + super + channel.about = id.content if id + channel.dc_dates.replace(prev_dc_dates) + end + + def setup_maker_elements(channel) + super + items = channel.maker.items + entries.each do |entry| + entry.setup_maker(items) + end + end + + class Author < RSS::Element + include CommonModel + include PersonConstruct + end + + class Category < RSS::Element + include CommonModel + + [ + ["term", "", true], + ["scheme", "", false, [nil, :uri]], + ["label", ""], + ].each do |name, uri, required, type| + install_get_attribute(name, uri, required, type) + end + + private + def maker_target(target) + target.new_category + end + end + + class Contributor < RSS::Element + include CommonModel + include PersonConstruct + end + + class Generator < RSS::Element + include CommonModel + include ContentModel + + [ + ["uri", "", false, [nil, :uri]], + ["version", ""], + ].each do |name, uri, required, type| + install_get_attribute(name, uri, required, type) + end + + private + def setup_maker_attributes(target) + target.generator do |generator| + generator.uri = uri if uri + generator.version = version if version + end + end + end + + class Icon < RSS::Element + include CommonModel + include URIContentModel + end + + class Id < RSS::Element + include CommonModel + include URIContentModel + end + + class Link < RSS::Element + include CommonModel + + [ + ["href", "", true, [nil, :uri]], + ["rel", ""], + ["type", ""], + ["hreflang", ""], + ["title", ""], + ["length", ""], + ].each do |name, uri, required, type| + install_get_attribute(name, uri, required, type) + end + + private + def maker_target(target) + target.new_link + end + end + + class Logo < RSS::Element + include CommonModel + include URIContentModel + + def maker_target(target) + target.maker.image + end + + private + def setup_maker_element_writer + "url=" + end + end + + class Rights < RSS::Element + include CommonModel + include TextConstruct + end + + class Subtitle < RSS::Element + include CommonModel + include TextConstruct + end + + class Title < RSS::Element + include CommonModel + include TextConstruct + end + + class Updated < RSS::Element + include CommonModel + include DateConstruct + end + + class Entry < RSS::Element + include CommonModel + include DuplicateLinkChecker + + [ + ["author", "*", :children], + ["category", "*", :children, "categories"], + ["content", "?", :child], + ["contributor", "*", :children], + ["id", nil, nil, :content], + ["link", "*", :children], + ["published", "?", :child, :content], + ["rights", "?", :child], + ["source", "?"], + ["summary", "?", :child], + ["title", nil], + ["updated", nil, :child, :content], + ].each do |tag, occurs, type, *args| + type ||= :attribute + __send__("install_have_#{type}_element", + tag, URI, occurs, tag, *args) + end + + def have_author?(check_parent=true) + authors.any? {|author| !author.to_s.empty?} or + (check_parent and @parent and @parent.have_author?) or + (source and source.have_author?) + end + + private + def atom_validate(ignore_unknown_element, tags, uri) + unless have_author? + raise MissingTagError.new("author", tag_name) + end + validate_duplicate_links(links) + end + + def have_required_elements? + super and have_author? + end + + def maker_target(items) + if items.respond_to?("items") + # For backward compatibility + items = items.items + end + items.new_item + end + + Author = Feed::Author + Category = Feed::Category + + class Content < RSS::Element + include CommonModel + + class << self + def xml_setter + "xml=" + end + + def xml_getter + "xml" + end + end + + [ + ["type", ""], + ["src", "", false, [nil, :uri]], + ].each do |name, uri, required, type| + install_get_attribute(name, uri, required, type) + end + + content_setup + add_need_initialize_variable("xml") + + attr_writer :xml + def have_xml_content? + inline_xhtml? or inline_other_xml? + end + + def xml + return @xml unless inline_xhtml? + return @xml if @xml.nil? + if @xml.is_a?(XML::Element) and + [@xml.name, @xml.uri] == ["div", XHTML_URI] + return @xml + end + + children = @xml + children = [children] unless children.is_a?(Array) + XML::Element.new("div", nil, XHTML_URI, + {"xmlns" => XHTML_URI}, children) + end + + def xhtml + if inline_xhtml? + xml + else + nil + end + end + + def atom_validate(ignore_unknown_element, tags, uri) + if out_of_line? + raise MissingAttributeError.new(tag_name, "type") if @type.nil? + unless (content.nil? or content.empty?) + raise NotAvailableValueError.new(tag_name, content) + end + elsif inline_xhtml? + if @xml.nil? + raise MissingTagError.new("div", tag_name) + end + unless @xml.name == "div" and @xml.uri == XHTML_URI + raise NotExpectedTagError.new(@xml.name, @xml.uri, tag_name) + end + end + end + + def inline_text? + !out_of_line? and [nil, "text", "html"].include?(@type) + end + + def inline_html? + return false if out_of_line? + @type == "html" or mime_split == ["text", "html"] + end + + def inline_xhtml? + !out_of_line? and @type == "xhtml" + end + + def inline_other? + return false if out_of_line? + media_type, subtype = mime_split + return false if media_type.nil? or subtype.nil? + true + end + + def inline_other_text? + return false unless inline_other? + return false if inline_other_xml? + + media_type, subtype = mime_split + return true if "text" == media_type.downcase + false + end + + def inline_other_xml? + return false unless inline_other? + + media_type, subtype = mime_split + normalized_mime_type = "#{media_type}/#{subtype}".downcase + if /(?:\+xml|^xml)$/ =~ subtype or + %w(text/xml-external-parsed-entity + application/xml-external-parsed-entity + application/xml-dtd).find {|x| x == normalized_mime_type} + return true + end + false + end + + def inline_other_base64? + inline_other? and !inline_other_text? and !inline_other_xml? + end + + def out_of_line? + not @src.nil? + end + + def mime_split + media_type = subtype = nil + if /\A\s*([a-z]+)\/([a-z\+]+)\s*(?:;.*)?\z/i =~ @type.to_s + media_type = $1.downcase + subtype = $2.downcase + end + [media_type, subtype] + end + + def need_base64_encode? + inline_other_base64? + end + + private + def empty_content? + out_of_line? or super + end + end + + Contributor = Feed::Contributor + Id = Feed::Id + Link = Feed::Link + + class Published < RSS::Element + include CommonModel + include DateConstruct + end + + Rights = Feed::Rights + + class Source < RSS::Element + include CommonModel + + [ + ["author", "*", :children], + ["category", "*", :children, "categories"], + ["contributor", "*", :children], + ["generator", "?"], + ["icon", "?"], + ["id", "?", nil, :content], + ["link", "*", :children], + ["logo", "?"], + ["rights", "?"], + ["subtitle", "?"], + ["title", "?"], + ["updated", "?", nil, :content], + ].each do |tag, occurs, type, *args| + type ||= :attribute + __send__("install_have_#{type}_element", + tag, URI, occurs, tag, *args) + end + + def have_author? + !author.to_s.empty? + end + + Author = Feed::Author + Category = Feed::Category + Contributor = Feed::Contributor + Generator = Feed::Generator + Icon = Feed::Icon + Id = Feed::Id + Link = Feed::Link + Logo = Feed::Logo + Rights = Feed::Rights + Subtitle = Feed::Subtitle + Title = Feed::Title + Updated = Feed::Updated + end + + class Summary < RSS::Element + include CommonModel + include TextConstruct + end + + Title = Feed::Title + Updated = Feed::Updated + end + end + + class Entry < RSS::Element + include RootElementMixin + include CommonModel + include DuplicateLinkChecker + + [ + ["author", "*", :children], + ["category", "*", :children, "categories"], + ["content", "?"], + ["contributor", "*", :children], + ["id", nil, nil, :content], + ["link", "*", :children], + ["published", "?", :child, :content], + ["rights", "?"], + ["source", "?"], + ["summary", "?"], + ["title", nil], + ["updated", nil, nil, :content], + ].each do |tag, occurs, type, *args| + type ||= :attribute + __send__("install_have_#{type}_element", + tag, URI, occurs, tag, *args) + end + + def initialize(version=nil, encoding=nil, standalone=nil) + super("1.0", version, encoding, standalone) + @feed_type = "atom" + @feed_subtype = "entry" + end + + def items + [self] + end + + def setup_maker(maker) + maker = maker.maker if maker.respond_to?("maker") + super(maker) + end + + def have_author? + authors.any? {|author| !author.to_s.empty?} or + (source and source.have_author?) + end + + private + def atom_validate(ignore_unknown_element, tags, uri) + unless have_author? + raise MissingTagError.new("author", tag_name) + end + validate_duplicate_links(links) + end + + def have_required_elements? + super and have_author? + end + + def maker_target(maker) + maker.items.new_item + end + + Author = Feed::Entry::Author + Category = Feed::Entry::Category + Content = Feed::Entry::Content + Contributor = Feed::Entry::Contributor + Id = Feed::Entry::Id + Link = Feed::Entry::Link + Published = Feed::Entry::Published + Rights = Feed::Entry::Rights + Source = Feed::Entry::Source + Summary = Feed::Entry::Summary + Title = Feed::Entry::Title + Updated = Feed::Entry::Updated + end + end + + Atom::CommonModel::ELEMENTS.each do |name| + BaseListener.install_get_text_element(Atom::URI, name, "#{name}=") + end + + module ListenerMixin + private + def initial_start_feed(tag_name, prefix, attrs, ns) + check_ns(tag_name, prefix, ns, Atom::URI) + + @rss = Atom::Feed.new(@version, @encoding, @standalone) + @rss.do_validate = @do_validate + @rss.xml_stylesheets = @xml_stylesheets + @rss.lang = attrs["xml:lang"] + @rss.base = attrs["xml:base"] + @last_element = @rss + pr = Proc.new do |text, tags| + @rss.validate_for_stream(tags) if @do_validate + end + @proc_stack.push(pr) + end + + def initial_start_entry(tag_name, prefix, attrs, ns) + check_ns(tag_name, prefix, ns, Atom::URI) + + @rss = Atom::Entry.new(@version, @encoding, @standalone) + @rss.do_validate = @do_validate + @rss.xml_stylesheets = @xml_stylesheets + @rss.lang = attrs["xml:lang"] + @rss.base = attrs["xml:base"] + @last_element = @rss + pr = Proc.new do |text, tags| + @rss.validate_for_stream(tags) if @do_validate + end + @proc_stack.push(pr) + end + end +end diff --git a/lib/rss/content.rb b/lib/rss/content.rb index 1b13f39fcf..bb61d9ebc5 100644 --- a/lib/rss/content.rb +++ b/lib/rss/content.rb @@ -32,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 d928c48223..415a319188 100644 --- a/lib/rss/converter.rb +++ b/lib/rss/converter.rb @@ -80,8 +80,12 @@ module RSS end rescue LoadError require 'nkf' - def_convert(1) do |value| - "NKF.nkf(#{nkf_arg.dump}, #{value})" + 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 diff --git a/lib/rss/dublincore.rb b/lib/rss/dublincore.rb index 8a4afd4dd9..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) @@ -36,8 +33,20 @@ 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 EOC end end @@ -61,7 +70,7 @@ module RSS "language" => nil, "relation" => nil, "coverage" => nil, - "rights" => "rightses" # FIXME + "rights" => "rights_list" } DATE_ELEMENTS = { @@ -122,13 +131,14 @@ 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 @@ -138,13 +148,6 @@ module RSS # 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}") @@ -152,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 a9e9e9094e..c4714aea12 100644 --- a/lib/rss/image.rb +++ b/lib/rss/image.rb @@ -4,7 +4,7 @@ require 'rss/dublincore' module RSS IMAGE_PREFIX = 'image' - IMAGE_URI = 'http://web.resource.org/rss/1.0/modules/image/' + IMAGE_URI = 'http://purl.org/rss/1.0/modules/image/' RDF.install_ns(IMAGE_PREFIX, IMAGE_URI) @@ -69,7 +69,7 @@ module RSS disp_name = "#{IMAGE_PREFIX}:#{tag}" install_text_element(tag, IMAGE_URI, "?", full_name, :integer, disp_name) - BaseListener.install_get_text_element(IMAGE_URI, tag, "#{full_name}=") + BaseListener.install_get_text_element(IMAGE_URI, tag, full_name) end alias width= image_width= @@ -142,8 +142,8 @@ module RSS end AVAILABLE_SIZES = %w(small medium large) - alias_method :_size=, :size= - private :_size= + alias_method :set_size, :size= + private :set_size def size=(new_value) if @do_validate and !new_value.nil? new_value = new_value.strip @@ -152,7 +152,7 @@ module RSS raise NotAvailableValueError.new(full_name, new_value, attr_name) end end - __send__(:_size=, new_value) + set_size(new_value) end alias image_size= size= diff --git a/lib/rss/itunes.rb b/lib/rss/itunes.rb new file mode 100644 index 0000000000..f95ca7aa2e --- /dev/null +++ b/lib/rss/itunes.rb @@ -0,0 +1,410 @@ +require 'rss/2.0' + +module RSS + ITUNES_PREFIX = 'itunes' + ITUNES_URI = 'http://www.itunes.com/dtds/podcast-1.0.dtd' + + Rss.install_ns(ITUNES_PREFIX, ITUNES_URI) + + module ITunesModelUtils + include Utils + + def def_class_accessor(klass, name, type, *args) + normalized_name = name.gsub(/-/, "_") + full_name = "#{ITUNES_PREFIX}_#{normalized_name}" + klass_name = "ITunes#{Utils.to_class_name(normalized_name)}" + + case type + when :element, :attribute + klass::ELEMENTS << full_name + def_element_class_accessor(klass, name, full_name, klass_name, *args) + when :elements + klass::ELEMENTS << full_name + def_elements_class_accessor(klass, name, full_name, klass_name, *args) + else + klass.install_must_call_validator(ITUNES_PREFIX, ITUNES_URI) + klass.install_text_element(normalized_name, ITUNES_URI, "?", + full_name, type, name) + end + end + + def def_element_class_accessor(klass, name, full_name, klass_name, + recommended_attribute_name=nil) + klass.install_have_child_element(name, ITUNES_PREFIX, "?", full_name) + end + + def def_elements_class_accessor(klass, name, full_name, klass_name, + plural_name, recommended_attribute_name=nil) + full_plural_name = "#{ITUNES_PREFIX}_#{plural_name}" + klass.install_have_children_element(name, ITUNES_PREFIX, "*", + full_name, full_plural_name) + end + end + + module ITunesBaseModel + extend ITunesModelUtils + + ELEMENTS = [] + + ELEMENT_INFOS = [["author"], + ["block", :yes_other], + ["explicit", :yes_clean_other], + ["keywords", :csv], + ["subtitle"], + ["summary"]] + end + + module ITunesChannelModel + extend BaseModel + extend ITunesModelUtils + include ITunesBaseModel + + ELEMENTS = [] + + class << self + def append_features(klass) + super + + return if klass.instance_of?(Module) + ELEMENT_INFOS.each do |name, type, *additional_infos| + def_class_accessor(klass, name, type, *additional_infos) + end + end + end + + ELEMENT_INFOS = [ + ["category", :elements, "categories", "text"], + ["image", :attribute, "href"], + ["owner", :element], + ["new-feed-url"], + ] + ITunesBaseModel::ELEMENT_INFOS + + class ITunesCategory < Element + include RSS09 + + @tag_name = "category" + + class << self + def required_prefix + ITUNES_PREFIX + end + + def required_uri + ITUNES_URI + end + end + + [ + ["text", "", true] + ].each do |name, uri, required| + install_get_attribute(name, uri, required) + end + + ITunesCategory = self + install_have_children_element("category", ITUNES_URI, "*", + "#{ITUNES_PREFIX}_category", + "#{ITUNES_PREFIX}_categories") + + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.text = args[0] + end + end + + def full_name + tag_name_with_prefix(ITUNES_PREFIX) + end + + private + def maker_target(categories) + if text or !itunes_categories.empty? + categories.new_category + else + nil + end + end + + def setup_maker_attributes(category) + category.text = text if text + end + + def setup_maker_elements(category) + super(category) + itunes_categories.each do |sub_category| + sub_category.setup_maker(category) + end + end + end + + class ITunesImage < Element + include RSS09 + + @tag_name = "image" + + class << self + def required_prefix + ITUNES_PREFIX + end + + def required_uri + ITUNES_URI + end + end + + [ + ["href", "", true] + ].each do |name, uri, required| + install_get_attribute(name, uri, required) + end + + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.href = args[0] + end + end + + def full_name + tag_name_with_prefix(ITUNES_PREFIX) + end + + private + def maker_target(target) + if href + target.itunes_image {|image| image} + else + nil + end + end + + def setup_maker_attributes(image) + image.href = href + end + end + + class ITunesOwner < Element + include RSS09 + + @tag_name = "owner" + + class << self + def required_prefix + ITUNES_PREFIX + end + + def required_uri + ITUNES_URI + end + end + + install_must_call_validator(ITUNES_PREFIX, ITUNES_URI) + [ + ["name"], + ["email"], + ].each do |name,| + ITunesBaseModel::ELEMENT_INFOS << name + install_text_element(name, ITUNES_URI, nil, "#{ITUNES_PREFIX}_#{name}") + end + + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + self.itunes_name = args[0] + self.itunes_email = args[1] + end + end + + def full_name + tag_name_with_prefix(ITUNES_PREFIX) + end + + private + def maker_target(target) + target.itunes_owner + end + + def setup_maker_element(owner) + super(owner) + owner.itunes_name = itunes_name + owner.itunes_email = itunes_email + end + end + end + + module ITunesItemModel + extend BaseModel + extend ITunesModelUtils + include ITunesBaseModel + + class << self + def append_features(klass) + super + + return if klass.instance_of?(Module) + ELEMENT_INFOS.each do |name, type| + def_class_accessor(klass, name, type) + end + end + end + + ELEMENT_INFOS = ITunesBaseModel::ELEMENT_INFOS + + [["duration", :element, "content"]] + + class ITunesDuration < Element + include RSS09 + + @tag_name = "duration" + + class << self + def required_prefix + ITUNES_PREFIX + end + + def required_uri + ITUNES_URI + end + + def parse(duration, do_validate=true) + if do_validate and /\A(?: + \d?\d:[0-5]\d:[0-5]\d| + [0-5]?\d:[0-5]\d + )\z/x !~ duration + raise ArgumentError, + "must be one of HH:MM:SS, H:MM:SS, MM::SS, M:SS: " + + duration.inspect + end + + components = duration.split(':') + components[3..-1] = nil if components.size > 3 + + components.unshift("00") until components.size == 3 + + components.collect do |component| + component.to_i + end + end + + def construct(hour, minute, second) + components = [minute, second] + if components.include?(nil) + nil + else + components.unshift(hour) if hour and hour > 0 + components.collect do |component| + "%02d" % component + end.join(":") + end + end + end + + content_setup + alias_method(:value, :content) + remove_method(:content=) + + attr_reader :hour, :minute, :second + def initialize(*args) + if Utils.element_initialize_arguments?(args) + super + else + super() + args = args[0] if args.size == 1 and args[0].is_a?(Array) + if args.size == 1 + self.content = args[0] + elsif args.size > 3 + raise ArgumentError, + "must be (do_validate, params), (content), " + + "(minute, second), ([minute, second]), " + + "(hour, minute, second) or ([hour, minute, second]): " + + args.inspect + else + @second, @minute, @hour = args.reverse + update_content + end + end + end + + def content=(value) + if value.nil? + @content = nil + elsif value.is_a?(self.class) + self.content = value.content + else + begin + @hour, @minute, @second = self.class.parse(value, @do_validate) + rescue ArgumentError + raise NotAvailableValueError.new(tag_name, value) + end + @content = value + end + end + alias_method(:value=, :content=) + + def hour=(hour) + @hour = @do_validate ? Integer(hour) : hour.to_i + update_content + hour + end + + def minute=(minute) + @minute = @do_validate ? Integer(minute) : minute.to_i + update_content + minute + end + + def second=(second) + @second = @do_validate ? Integer(second) : second.to_i + update_content + second + end + + def full_name + tag_name_with_prefix(ITUNES_PREFIX) + end + + private + def update_content + @content = self.class.construct(hour, minute, second) + end + + def maker_target(target) + if @content + target.itunes_duration {|duration| duration} + else + nil + end + end + + def setup_maker_element(duration) + super(duration) + duration.content = @content + end + end + end + + class Rss + class Channel + include ITunesChannelModel + class Item; include ITunesItemModel; end + end + end + + element_infos = + ITunesChannelModel::ELEMENT_INFOS + ITunesItemModel::ELEMENT_INFOS + element_infos.each do |name, type| + case type + when :element, :elements, :attribute + class_name = Utils.to_class_name(name) + BaseListener.install_class_name(ITUNES_URI, name, "ITunes#{class_name}") + else + accessor_base = "#{ITUNES_PREFIX}_#{name.gsub(/-/, '_')}" + BaseListener.install_get_text_element(ITUNES_URI, name, accessor_base) + end + end +end diff --git a/lib/rss/maker.rb b/lib/rss/maker.rb index 9ed799ac7f..666dfa872d 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,19 +19,26 @@ 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/slash" require "rss/maker/syndication" require "rss/maker/taxonomy" require "rss/maker/trackback" require "rss/maker/image" +require "rss/maker/itunes" diff --git a/lib/rss/maker/0.9.rb b/lib/rss/maker/0.9.rb index b82585fb96..c83597dfd5 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) @@ -21,41 +22,34 @@ module RSS end 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 +57,405 @@ 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 {|d| d.have_required_values?} + vars << "title" unless title {|t| t.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 {|t| t.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 + def required_variable_names + %w(link) + end + + def not_set_required_variables + vars = super + vars << "title" unless title {|t| t.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..c4af6e9b2e 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 {|d| d.have_required_values?} + vars << "title" unless title {|t| t.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 {|t| t.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..9149c0e24b 100644 --- a/lib/rss/maker/2.0.rb +++ b/lib/rss/maker/2.0.rb @@ -7,18 +7,15 @@ 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) + %w(link) end class SkipDays < RSS09::Channel::SkipDays @@ -32,137 +29,186 @@ 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 - class Item < RSS09::Items::Item - - def have_required_values? - @title or @description + private + def required_variable_names + %w(title description) end - private 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..fd3198cd9e --- /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_method(:uri=, :content=) + end + set = setup_values(logo) + class << logo + remove_method(:uri=) + end + if set + current.logo = logo + set_parent(logo, current) + setup_other_elements(feed) + elsif variable_is_set? + raise NotSetError.new(self.class.not_set_name, + not_set_required_variables) + end + end + + private + def required_variable_names + %w(uri) + end + end + end +end diff --git a/lib/rss/maker/base.rb b/lib/rss/maker/base.rb index 2327dd98e4..fdb8946efc 100644 --- a/lib/rss/maker/base.rb +++ b/lib/rss/maker/base.rb @@ -4,70 +4,184 @@ require 'rss/rss' module RSS module Maker + class Base + extend Utils::InheritedReader - module Base + OTHER_ELEMENTS = [] + NEED_INITIALIZE_VARIABLES = [] - def self.append_features(klass) - super - - klass.module_eval(<<-EOC, __FILE__, __LINE__) + class << self + def other_elements + inherited_array_reader("OTHER_ELEMENTS") + end + def need_initialize_variables + inherited_array_reader("NEED_INITIALIZE_VARIABLES") + end - OTHER_ELEMENTS = [] - NEED_INITIALIZE_VARIABLES = [] + def inherited_base + ::RSS::Maker::Base + end - def self.inherited(subclass) + def inherited(subclass) subclass.const_set("OTHER_ELEMENTS", []) subclass.const_set("NEED_INITIALIZE_VARIABLES", []) + end + + def add_other_element(variable_name) + self::OTHER_ELEMENTS << variable_name + end + + def add_need_initialize_variable(variable_name, init_value="nil") + self::NEED_INITIALIZE_VARIABLES << [variable_name, init_value] + end - subclass.module_eval(<<-EOEOC, __FILE__, __LINE__) - def self.other_elements - OTHER_ELEMENTS + super + def def_array_element(name, plural=nil, klass_name=nil) + include Enumerable + extend Forwardable + + plural ||= "#{name}s" + klass_name ||= Utils.to_class_name(name) + def_delegators("@#{plural}", :<<, :[], :[]=, :first, :last) + def_delegators("@#{plural}", :push, :pop, :shift, :unshift) + def_delegators("@#{plural}", :each, :size, :empty?, :clear) + + add_need_initialize_variable(plural, "[]") + + module_eval(<<-EOC, __FILE__, __LINE__ + 1) + def new_#{name} + #{name} = self.class::#{klass_name}.new(@maker) + @#{plural} << #{name} + if block_given? + yield #{name} + else + #{name} + end end + alias new_child new_#{name} - def self.need_initialize_variables - NEED_INITIALIZE_VARIABLES + super + def to_feed(*args) + @#{plural}.each do |#{name}| + #{name}.to_feed(*args) + end end - EOEOC - end - def self.add_other_element(variable_name) - OTHER_ELEMENTS << variable_name + def replace(elements) + @#{plural}.replace(elements.to_a) + end + EOC end - def self.other_elements - OTHER_ELEMENTS + def def_classed_element_without_accessor(name, class_name=nil) + class_name ||= Utils.to_class_name(name) + add_other_element(name) + add_need_initialize_variable(name, "make_#{name}") + module_eval(<<-EOC, __FILE__, __LINE__ + 1) + private + def setup_#{name}(feed, current) + @#{name}.to_feed(feed, current) + end + + def make_#{name} + self.class::#{class_name}.new(@maker) + end + EOC + end + + def def_classed_element(name, class_name=nil, attribute_name=nil) + def_classed_element_without_accessor(name, class_name) + if attribute_name + module_eval(<<-EOC, __FILE__, __LINE__ + 1) + def #{name} + if block_given? + yield(@#{name}) + else + @#{name}.#{attribute_name} + end + end + + def #{name}=(new_value) + @#{name}.#{attribute_name} = new_value + end + EOC + else + attr_reader name + end end - def self.add_need_initialize_variable(variable_name, init_value="nil") - NEED_INITIALIZE_VARIABLES << [variable_name, init_value] + def def_classed_elements(name, attribute, plural_class_name=nil, + plural_name=nil, new_name=nil) + plural_name ||= "#{name}s" + new_name ||= name + def_classed_element(plural_name, plural_class_name) + local_variable_name = "_#{name}" + new_value_variable_name = "new_value" + additional_setup_code = nil + if block_given? + additional_setup_code = yield(local_variable_name, + new_value_variable_name) + end + module_eval(<<-EOC, __FILE__, __LINE__ + 1) + def #{name} + #{local_variable_name} = #{plural_name}.first + #{local_variable_name} ? #{local_variable_name}.#{attribute} : nil + end + + def #{name}=(#{new_value_variable_name}) + #{local_variable_name} = + #{plural_name}.first || #{plural_name}.new_#{new_name} + #{additional_setup_code} + #{local_variable_name}.#{attribute} = #{new_value_variable_name} + end + EOC end - def self.need_initialize_variables - NEED_INITIALIZE_VARIABLES + def def_other_element(name) + attr_accessor name + def_other_element_without_accessor(name) end - def self.def_array_element(name) - include Enumerable - extend Forwardable + def def_other_element_without_accessor(name) + add_need_initialize_variable(name) + add_other_element(name) + module_eval(<<-EOC, __FILE__, __LINE__ + 1) + def setup_#{name}(feed, current) + if !@#{name}.nil? and current.respond_to?(:#{name}=) + current.#{name} = @#{name} + end + end + EOC + end - def_delegators("@\#{name}", :<<, :[], :[]=, :first, :last) - def_delegators("@\#{name}", :push, :pop, :shift, :unshift) - def_delegators("@\#{name}", :each, :size) - - add_need_initialize_variable(name, "[]") + def def_csv_element(name, type=nil) + def_other_element_without_accessor(name) + attr_reader(name) + converter = "" + if type == :integer + converter = "{|v| Integer(v)}" + end + module_eval(<<-EOC, __FILE__, __LINE__ + 1) + def #{name}=(value) + @#{name} = Utils::CSV.parse(value)#{converter} + end + EOC end - 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 +189,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 +232,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 +244,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,12 +256,106 @@ module RSS end true end - end - class RSSBase - include Base + module AtomPersonConstructBase + def self.append_features(klass) + super + + klass.class_eval(<<-EOC, __FILE__, __LINE__ + 1) + %w(name uri email).each do |element| + attr_accessor element + add_need_initialize_variable(element) + end + EOC + end + end + + module AtomTextConstructBase + module EnsureXMLContent + class << self + def included(base) + super + base.class_eval do + %w(type content xml_content).each do |element| + attr_reader element + attr_writer element if element != "xml_content" + add_need_initialize_variable(element) + end + + alias_method(:xhtml, :xml_content) + end + end + end + + def ensure_xml_content(content) + xhtml_uri = ::RSS::Atom::XHTML_URI + unless content.is_a?(RSS::XML::Element) and + ["div", xhtml_uri] == [content.name, content.uri] + children = content + children = [children] unless content.is_a?(Array) + children = set_xhtml_uri_as_default_uri(children) + content = RSS::XML::Element.new("div", nil, xhtml_uri, + {"xmlns" => xhtml_uri}, + children) + end + content + end + + def xml_content=(content) + @xml_content = ensure_xml_content(content) + end + + def xhtml=(content) + self.xml_content = content + end + + private + def set_xhtml_uri_as_default_uri(children) + children.collect do |child| + if child.is_a?(RSS::XML::Element) and + child.prefix.nil? and child.uri.nil? + RSS::XML::Element.new(child.name, nil, ::RSS::Atom::XHTML_URI, + child.attributes.dup, + set_xhtml_uri_as_default_uri(child.children)) + else + child + end + end + end + end + def self.append_features(klass) + super + + klass.class_eval do + include EnsureXMLContent + end + end + end + + module SetupDefaultDate + private + def _set_default_values(&block) + keep = { + :date => date, + :dc_dates => dc_dates.to_a.dup, + } + _date = date + if _date and !dc_dates.any? {|dc_date| dc_date.value == _date} + dc_date = self.class::DublinCoreDates::DublinCoreDate.new(self) + dc_date.value = _date.dup + dc_dates.unshift(dc_date) + end + self.date ||= self.dc_date + super(&block) + ensure + date = keep[:date] + dc_dates.replace(keep[:dc_dates]) + end + end + + class RSSBase < Base class << self def make(&block) new.make(&block) @@ -143,22 +367,25 @@ 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} self.class::#{Utils.to_class_name(element)}.new(self) end -EOC + 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 +394,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,51 +417,27 @@ 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 - if block_given? - yield xss - else - xss - end - end + class XMLStyleSheets < Base + def_array_element("xml_stylesheet", nil, "XMLStyleSheet") - class XMLStyleSheet - include Base + class XMLStyleSheet < Base ::RSS::XMLStyleSheet::ATTRIBUTES.each do |attribute| attr_accessor attribute 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? @@ -242,172 +445,183 @@ EOC @type = xss.type end end + + def required_variable_names + %w(href type) + end end end - class ChannelBase - include Base + class ChannelBase < Base + include SetupDefaultDate - %w(cloud categories skipDays skipHours).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) - end + %w(cloud categories skipDays skipHours).each do |name| + def_classed_element(name) + end - def make_#{element} - self.class::#{Utils.to_class_name(element)}.new(@maker) - end -EOC + %w(generator copyright description title).each do |name| + def_classed_element(name, nil, "content") end - %w(about title link description language copyright + [ + ["link", "href", Proc.new {|target,| "#{target}.href = 'self'"}], + ["author", "name"], + ["contributor", "name"], + ].each do |name, attribute, additional_setup_maker| + def_classed_elements(name, attribute, &additional_setup_maker) + end + + %w(id about language managingEditor webMaster rating docs date - lastBuildDate 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 current_element(rss) - rss.channel + def pubDate=(date) + self.date = date end - class SkipDaysBase - include Base + def updated + date + end - def_array_element("days") + def updated=(date) + self.date = date + end - def new_day - day = self.class::Day.new(@maker) - @days << day - if block_given? - yield day - else - day - end - end - - def current_element(rss) - rss.channel.skipDays - end + alias_method(:rights, :copyright) + alias_method(:rights=, :copyright=) - class DayBase - include Base - - %w(content).each do |element| - attr_accessor element - add_need_initialize_variable(element) - end + alias_method(:subtitle, :description) + alias_method(:subtitle=, :description=) - def current_element(rss) - rss.channel.skipDays.last - end + def icon + image_favicon.about + end - end + def icon=(url) + image_favicon.about = url end - - class SkipHoursBase - include Base - def_array_element("hours") + def logo + maker.image.url + end - def new_hour - hour = self.class::Hour.new(@maker) - @hours << hour - if block_given? - yield hour - else - hour - end - end - - def current_element(rss) - rss.channel.skipHours - end + def logo=(url) + maker.image.url = url + end - class HourBase - include Base - + class SkipDaysBase < Base + def_array_element("day") + + class DayBase < Base %w(content).each do |element| attr_accessor element add_need_initialize_variable(element) end + end + end + + class SkipHoursBase < Base + def_array_element("hour") - def current_element(rss) - rss.channel.skipHours.last + class HourBase < Base + %w(content).each do |element| + attr_accessor element + add_need_initialize_variable(element) end - end end - class CloudBase - include Base - + class CloudBase < Base %w(domain port path registerProcedure protocol).each do |element| 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") + class CategoriesBase < Base + def_array_element("category", "categories") - def new_category - category = self.class::Category.new(@maker) - @categories << category - if block_given? - yield category - else - category + class CategoryBase < Base + %w(domain content label).each do |element| + attr_accessor element + add_need_initialize_variable(element) end + + alias_method(:term, :domain) + alias_method(:term=, :domain=) + alias_method(:scheme, :content) + alias_method(:scheme=, :content=) end + end - class CategoryBase - include Base + class LinksBase < Base + def_array_element("link") - %w(domain content).each do |element| + class LinkBase < Base + %w(href rel type hreflang title length).each do |element| attr_accessor element add_need_initialize_variable(element) end end end + + class AuthorsBase < Base + def_array_element("author") + + class AuthorBase < Base + include AtomPersonConstructBase + end + end + + class ContributorsBase < Base + def_array_element("contributor") + + class ContributorBase < Base + include AtomPersonConstructBase + end + end + + class GeneratorBase < Base + %w(uri version content).each do |element| + attr_accessor element + add_need_initialize_variable(element) + end + end + + class CopyrightBase < Base + include AtomTextConstructBase + end + + class DescriptionBase < Base + include AtomTextConstructBase + end + + class TitleBase < Base + include AtomTextConstructBase + end end - class ImageBase - include Base - + class ImageBase < Base %w(title url width height description).each do |element| attr_accessor element add_need_initialize_variable(element) end - + def link @maker.channel.link end - - def current_element(rss) - rss.image - end end - class ItemsBase - include Base + class ItemsBase < Base + def_array_element("item") - def_array_element("items") - attr_accessor :do_sort, :max_size def initialize(maker) @@ -423,21 +637,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 - if block_given? - yield item - else - item - end - end - private def sort_if_need if @do_sort.respond_to?(:call) @@ -453,94 +653,216 @@ EOC end end - class ItemBase - include Base - - %w(guid enclosure source categories).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) - end + class ItemBase < Base + include SetupDefaultDate - def make_#{element} - self.class::#{Utils.to_class_name(element)}.new(@maker) - end -EOC + %w(guid enclosure source categories content).each do |name| + def_classed_element(name) end - - %w(title link description date author comments).each do |element| + + %w(rights description title).each do |name| + def_classed_element(name, nil, "content") + end + + [ + ["author", "name"], + ["link", "href", Proc.new {|target,| "#{target}.href = 'alternate'"}], + ["contributor", "name"], + ].each do |name, attribute| + def_classed_elements(name, attribute) + end + + %w(date comments id published).each do |element| attr_accessor element add_need_initialize_variable(element) end - 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 + + alias_method(:summary, :description) + alias_method(:summary=, :description=) 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 + class GuidBase < Base %w(isPermaLink content).each do |element| attr_accessor element add_need_initialize_variable(element) end end - - class EnclosureBase - include Base + class EnclosureBase < Base %w(url length type).each do |element| attr_accessor element add_need_initialize_variable(element) end end - - class SourceBase - include Base - %w(url content).each do |element| + class SourceBase < Base + %w(authors categories contributors generator icon + logo rights subtitle title).each do |name| + def_classed_element(name) + end + + [ + ["link", "href"], + ].each do |name, attribute| + def_classed_elements(name, attribute) + end + + %w(id content date).each do |element| attr_accessor element add_need_initialize_variable(element) end + + alias_method(:url, :link) + alias_method(:url=, :link=) + + def updated + date + end + + def updated=(date) + self.date = date + end + + private + AuthorsBase = ChannelBase::AuthorsBase + CategoriesBase = ChannelBase::CategoriesBase + ContributorsBase = ChannelBase::ContributorsBase + GeneratorBase = ChannelBase::GeneratorBase + + class IconBase < Base + %w(url).each do |element| + attr_accessor element + add_need_initialize_variable(element) + end + end + + LinksBase = ChannelBase::LinksBase + + class LogoBase < Base + %w(uri).each do |element| + attr_accessor element + add_need_initialize_variable(element) + end + end + + class RightsBase < Base + include AtomTextConstructBase + end + + class SubtitleBase < Base + include AtomTextConstructBase + end + + class TitleBase < Base + include AtomTextConstructBase + end end - + CategoriesBase = ChannelBase::CategoriesBase - + AuthorsBase = ChannelBase::AuthorsBase + LinksBase = ChannelBase::LinksBase + ContributorsBase = ChannelBase::ContributorsBase + + class RightsBase < Base + include AtomTextConstructBase + end + + class DescriptionBase < Base + include AtomTextConstructBase + end + + class ContentBase < Base + include AtomTextConstructBase::EnsureXMLContent + + %w(src).each do |element| + attr_accessor(element) + add_need_initialize_variable(element) + end + + def xml_content=(content) + content = ensure_xml_content(content) if inline_xhtml? + @xml_content = content + end + + alias_method(:xml, :xml_content) + alias_method(:xml=, :xml_content=) + + def inline_text? + [nil, "text", "html"].include?(@type) + end + + def inline_html? + @type == "html" + end + + def inline_xhtml? + @type == "xhtml" + end + + def inline_other? + !out_of_line? and ![nil, "text", "html", "xhtml"].include?(@type) + end + + def inline_other_text? + return false if @type.nil? or out_of_line? + /\Atext\//i.match(@type) ? true : false + end + + def inline_other_xml? + return false if @type.nil? or out_of_line? + /[\+\/]xml\z/i.match(@type) ? true : false + end + + def inline_other_base64? + return false if @type.nil? or out_of_line? + @type.include?("/") and !inline_other_text? and !inline_other_xml? + end + + def out_of_line? + not @src.nil? and @content.nil? + end + end + + class TitleBase < Base + include AtomTextConstructBase + end end end - class TextinputBase - include Base - + class TextinputBase < Base %w(title description name link).each do |element| 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/content.rb b/lib/rss/maker/content.rb index 18590d0cf8..a1fd283116 100644 --- a/lib/rss/maker/content.rb +++ b/lib/rss/maker/content.rb @@ -7,17 +7,8 @@ module RSS def self.append_features(klass) super - ::RSS::ContentModel::ELEMENTS.each do |element| - klass.add_need_initialize_variable(element) - klass.add_other_element(element) - klass.module_eval(<<-EOC, __FILE__, __LINE__+1) - attr_accessor :#{element} - def setup_#{element}(rss, current) - if #{element} and current.respond_to?(:#{element}=) - current.#{element} = @#{element} if @#{element} - end - end - EOC + ::RSS::ContentModel::ELEMENTS.each do |name| + klass.def_other_element(name) end end end diff --git a/lib/rss/maker/dublincore.rb b/lib/rss/maker/dublincore.rb index 0cf1255e82..ff4813fe19 100644 --- a/lib/rss/maker/dublincore.rb +++ b/lib/rss/maker/dublincore.rb @@ -15,60 +15,40 @@ module RSS plural_klass_name = "DublinCore#{Utils.to_class_name(plural_name)}" full_plural_klass_name = "self.class::#{plural_klass_name}" full_klass_name = "#{full_plural_klass_name}::#{klass_name}" - klass.add_need_initialize_variable(full_plural_name, - "make_#{full_plural_name}") - klass.add_other_element(full_plural_name) - klass.module_eval(<<-EOC, __FILE__, __LINE__+1) - attr_accessor :#{full_plural_name} - def make_#{full_plural_name} - #{full_plural_klass_name}.new(@maker) - end - - def setup_#{full_plural_name}(rss, current) - @#{full_plural_name}.to_rss(rss, current) - end - - def #{full_name} - @#{full_plural_name}[0] and @#{full_plural_name}[0].value - end - - def #{full_name}=(new_value) - @#{full_plural_name}[0] = #{full_klass_name}.new(self) - @#{full_plural_name}[0].value = new_value + klass.def_classed_elements(full_name, "value", plural_klass_name, + full_plural_name, name) + klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1) + def new_#{full_name}(value=nil) + _#{full_name} = #{full_plural_name}.new_#{name} + _#{full_name}.value = value + if block_given? + yield _#{full_name} + else + _#{full_name} + end end -EOC + EOC end + + klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1) + # For backward compatibility + alias #{DC_PREFIX}_rightses #{DC_PREFIX}_rights_list + EOC end ::RSS::DublinCoreModel::ELEMENT_NAME_INFOS.each do |name, plural_name| plural_name ||= "#{name}s" + full_name ||= "#{DC_PREFIX}_#{name}" + full_plural_name ||= "#{DC_PREFIX}_#{plural_name}" klass_name = Utils.to_class_name(name) + full_klass_name = "DublinCore#{klass_name}" plural_klass_name = "DublinCore#{Utils.to_class_name(plural_name)}" - module_eval(<<-EOC, __FILE__, __LINE__) - 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} - if block_given? - yield #{name} - else - #{name} - end - end - - def to_rss(rss, current) - @#{plural_name}.each do |#{name}| - #{name}.to_rss(rss, current) - end - end - - class #{klass_name}Base - include Base + module_eval(<<-EOC, __FILE__, __LINE__ + 1) + class #{plural_klass_name}Base < Base + def_array_element(#{name.dump}, #{full_plural_name.dump}, + #{full_klass_name.dump}) + class #{full_klass_name}Base < Base attr_accessor :value add_need_initialize_variable("value") alias_method(:content, :value) @@ -77,7 +57,15 @@ EOC def have_required_values? @value end + + def to_feed(feed, current) + if value and current.respond_to?(:#{full_name}) + new_item = current.class::#{full_klass_name}.new(value) + current.#{full_plural_name} << new_item + end + end end + #{klass_name}Base = #{full_klass_name}Base end EOC end @@ -86,18 +74,13 @@ EOC ::RSS::DublinCoreModel::ELEMENT_NAME_INFOS.each do |name, plural_name| 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)) + plural_klass_name = "DublinCore#{Utils.to_class_name(plural_name)}" + klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1) class #{plural_klass_name} < #{plural_klass_name}Base - class #{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 + class #{full_klass_name} < #{full_klass_name}Base end + #{klass_name} = #{full_klass_name} end EOC end @@ -106,64 +89,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..be648832c3 --- /dev/null +++ b/lib/rss/maker/entry.rb @@ -0,0 +1,163 @@ +require "rss/maker/atom" +require "rss/maker/feed" + +module RSS + module Maker + module Atom + class Entry < RSSBase + def initialize + 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 + + private + def required_variable_names + %w(id updated) + end + + def variables + super + ["updated"] + end + + def variable_is_set? + super or !authors.empty? + end + + def not_set_required_variables + set_default_values do + vars = super + if authors.all? {|author| !author.have_required_values?} + vars << "author" + end + vars << "title" unless title {|t| t.have_required_values?} + vars + end + end + + def _set_default_values(&block) + keep = { + :authors => authors.to_a.dup, + :contributors => contributors.to_a.dup, + :categories => categories.to_a.dup, + :id => id, + :links => links.to_a.dup, + :rights => @rights, + :title => @title, + :updated => updated, + } + authors.replace(@maker.channel.authors) if keep[:authors].empty? + if keep[:contributors].empty? + contributors.replace(@maker.channel.contributors) + end + if keep[:categories].empty? + categories.replace(@maker.channel.categories) + end + self.id ||= link || @maker.channel.id + links.replace(@maker.channel.links) if keep[:links].empty? + unless keep[:rights].variable_is_set? + @maker.channel.rights {|r| @rights = r} + end + unless keep[:title].variable_is_set? + @maker.channel.title {|t| @title = t} + end + self.updated ||= @maker.channel.updated + super(&block) + ensure + authors.replace(keep[:authors]) + contributors.replace(keep[:contributors]) + categories.replace(keep[:categories]) + links.replace(keep[:links]) + self.id = keep[:id] + @rights = keep[:rights] + @title = keep[:title] + self.updated = keep[:prev_updated] + end + + Guid = Feed::Items::Item::Guid + Enclosure = Feed::Items::Item::Enclosure + Source = Feed::Items::Item::Source + Categories = Feed::Items::Item::Categories + Authors = Feed::Items::Item::Authors + Contributors = Feed::Items::Item::Contributors + Links = Feed::Items::Item::Links + Rights = Feed::Items::Item::Rights + Description = Feed::Items::Item::Description + Title = Feed::Items::Item::Title + Content = Feed::Items::Item::Content + end + end + + class Textinput < TextinputBase + end + end + end + + add_maker("atom:entry", 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..95ae735c6b --- /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 {|t| t.have_required_values?} + vars + end + + def _set_default_values(&block) + keep = { + :id => id, + :updated => updated, + } + self.id ||= about + self.updated ||= dc_date + super(&block) + ensure + self.id = keep[:id] + self.updated = keep[:updated] + end + + class SkipDays < SkipDaysBase + def to_feed(*args) + end + + class Day < DayBase + end + end + + class SkipHours < SkipHoursBase + def to_feed(*args) + end + + class Hour < HourBase + end + end + + class Cloud < CloudBase + def to_feed(*args) + end + end + + class Categories < CategoriesBase + class Category < CategoryBase + include AtomCategory + + def self.not_set_name + "maker.channel.category" + end + end + end + + class Links < LinksBase + class Link < LinkBase + include AtomLink + + def self.not_set_name + "maker.channel.link" + end + end + end + + AtomPersons.def_atom_persons(self, "author", "maker.channel.author") + AtomPersons.def_atom_persons(self, "contributor", + "maker.channel.contributor") + + class Generator < GeneratorBase + include AtomGenerator + + def self.not_set_name + "maker.channel.generator" + end + end + + AtomTextConstruct.def_atom_text_construct(self, "rights", + "maker.channel.copyright", + "Copyright") + AtomTextConstruct.def_atom_text_construct(self, "subtitle", + "maker.channel.description", + "Description") + AtomTextConstruct.def_atom_text_construct(self, "title", + "maker.channel.title") + end + + class Image < ImageBase + def to_feed(feed) + logo = feed.class::Logo.new + class << logo + alias_method(:url=, :content=) + end + set = setup_values(logo) + class << logo + remove_method(:url=) + end + if set + feed.logo = logo + set_parent(logo, feed) + setup_other_elements(feed, logo) + elsif variable_is_set? + raise NotSetError.new("maker.image", not_set_required_variables) + end + end + + private + def required_variable_names + %w(url) + end + end + + class Items < ItemsBase + def to_feed(feed) + normalize.each do |item| + item.to_feed(feed) + end + setup_other_elements(feed, feed.entries) + end + + class Item < ItemBase + def to_feed(feed) + set_default_values do + entry = feed.class::Entry.new + set = setup_values(entry) + setup_other_elements(feed, entry) + if set + feed.entries << entry + set_parent(entry, feed) + elsif variable_is_set? + raise NotSetError.new("maker.item", not_set_required_variables) + end + end + end + + def have_required_values? + set_default_values do + super and title {|t| t.have_required_values?} + end + end + + private + def required_variable_names + %w(id updated) + end + + def variables + super + ["updated"] + end + + def not_set_required_variables + vars = super + vars << "title" unless title {|t| t.have_required_values?} + vars + end + + def _set_default_values(&block) + keep = { + :id => id, + :updated => updated, + } + self.id ||= link + self.updated ||= dc_date + super(&block) + ensure + self.id = keep[:id] + self.updated = keep[:updated] + end + + class Guid < GuidBase + def to_feed(feed, current) + end + end + + class Enclosure < EnclosureBase + def to_feed(feed, current) + end + end + + class Source < SourceBase + def to_feed(feed, current) + source = current.class::Source.new + setup_values(source) + current.source = source + set_parent(source, current) + setup_other_elements(feed, source) + current.source = nil if source.to_s == "<source/>" + end + + private + def required_variable_names + [] + end + + def variables + super + ["updated"] + end + + AtomPersons.def_atom_persons(self, "author", + "maker.item.source.author") + AtomPersons.def_atom_persons(self, "contributor", + "maker.item.source.contributor") + + class Categories < CategoriesBase + class Category < CategoryBase + include AtomCategory + + def self.not_set_name + "maker.item.source.category" + end + end + end + + class Generator < GeneratorBase + include AtomGenerator + + def self.not_set_name + "maker.item.source.generator" + end + end + + class Icon < IconBase + def to_feed(feed, current) + icon = current.class::Icon.new + class << icon + alias_method(:url=, :content=) + end + set = setup_values(icon) + class << icon + remove_method(:url=) + end + if set + current.icon = icon + set_parent(icon, current) + setup_other_elements(feed, icon) + elsif variable_is_set? + raise NotSetError.new("maker.item.source.icon", + not_set_required_variables) + end + end + + private + def required_variable_names + %w(url) + end + end + + class Links < LinksBase + class Link < LinkBase + include AtomLink + + def self.not_set_name + "maker.item.source.link" + end + end + end + + class Logo < LogoBase + include AtomLogo + + def self.not_set_name + "maker.item.source.logo" + end + end + + maker_name_base = "maker.item.source." + maker_name = "#{maker_name_base}rights" + AtomTextConstruct.def_atom_text_construct(self, "rights", + maker_name) + maker_name = "#{maker_name_base}subtitle" + AtomTextConstruct.def_atom_text_construct(self, "subtitle", + maker_name) + maker_name = "#{maker_name_base}title" + AtomTextConstruct.def_atom_text_construct(self, "title", + maker_name) + end + + class Categories < CategoriesBase + class Category < CategoryBase + include AtomCategory + + def self.not_set_name + "maker.item.category" + end + end + end + + AtomPersons.def_atom_persons(self, "author", "maker.item.author") + AtomPersons.def_atom_persons(self, "contributor", + "maker.item.contributor") + + class Links < LinksBase + class Link < LinkBase + include AtomLink + + def self.not_set_name + "maker.item.link" + end + end + end + + AtomTextConstruct.def_atom_text_construct(self, "rights", + "maker.item.rights") + AtomTextConstruct.def_atom_text_construct(self, "summary", + "maker.item.description", + "Description") + AtomTextConstruct.def_atom_text_construct(self, "title", + "maker.item.title") + + class Content < ContentBase + def to_feed(feed, current) + content = current.class::Content.new + if setup_values(content) + content.src = nil if content.src and content.content + current.content = content + set_parent(content, current) + setup_other_elements(feed, content) + elsif variable_is_set? + raise NotSetError.new("maker.item.content", + not_set_required_variables) + end + end + + alias_method(:xml, :xml_content) + + private + def required_variable_names + if out_of_line? + %w(type) + elsif xml_type? + %w(xml_content) + else + %w(content) + end + end + + def variables + if out_of_line? + super + elsif xml_type? + super + %w(xml) + else + super + end + end + + def xml_type? + _type = type + return false if _type.nil? + _type == "xhtml" or + /(?:\+xml|\/xml)$/i =~ _type or + %w(text/xml-external-parsed-entity + application/xml-external-parsed-entity + application/xml-dtd).include?(_type.downcase) + end + end + end + end + + class Textinput < TextinputBase + end + end + end + + add_maker("atom", 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..b95cf4c714 100644 --- a/lib/rss/maker/image.rb +++ b/lib/rss/maker/image.rb @@ -9,24 +9,18 @@ module RSS super 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) - attr_reader :#{name} - def setup_#{name}(rss, current) - if @#{name} - @#{name}.to_rss(rss, current) - end - end + klass.def_classed_element(name) + end - def make_#{name} - self.class::#{Utils.to_class_name(name)}.new(@maker) + 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 + class ImageItemBase < Base include Maker::DublinCoreModel attr_accessor :about, :resource, :image_width, :image_height @@ -42,6 +36,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 @@ -50,24 +53,18 @@ EOC super name = "#{RSS::IMAGE_PREFIX}_favicon" - klass.add_need_initialize_variable(name, "make_#{name}") - klass.add_other_element(name) - klass.module_eval(<<-EOC, __FILE__, __LINE__+1) - attr_reader :#{name} - def setup_#{name}(rss, current) - if @#{name} - @#{name}.to_rss(rss, current) - end - end + klass.def_classed_element(name) + end - def make_#{name} - self.class::#{Utils.to_class_name(name)}.new(@maker) + def self.install_image_favicon(klass) + klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1) + class ImageFavicon < ImageFaviconBase + DublinCoreModel.install_dublin_core(self) end -EOC + EOC end - class ImageFaviconBase - include Base + class ImageFaviconBase < Base include Maker::DublinCoreModel attr_accessor :about, :image_size @@ -79,6 +76,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 +94,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/itunes.rb b/lib/rss/maker/itunes.rb new file mode 100644 index 0000000000..f02db28824 --- /dev/null +++ b/lib/rss/maker/itunes.rb @@ -0,0 +1,242 @@ +require 'rss/itunes' +require 'rss/maker/2.0' + +module RSS + module Maker + module ITunesBaseModel + def def_class_accessor(klass, name, type, *args) + name = name.gsub(/-/, "_").gsub(/^itunes_/, '') + full_name = "#{RSS::ITUNES_PREFIX}_#{name}" + case type + when nil + klass.def_other_element(full_name) + when :yes_other + def_yes_other_accessor(klass, full_name) + when :yes_clean_other + def_yes_clean_other_accessor(klass, full_name) + when :csv + def_csv_accessor(klass, full_name) + when :element, :attribute + recommended_attribute_name, = *args + klass_name = "ITunes#{Utils.to_class_name(name)}" + klass.def_classed_element(full_name, klass_name, + recommended_attribute_name) + when :elements + plural_name, recommended_attribute_name = args + plural_name ||= "#{name}s" + full_plural_name = "#{RSS::ITUNES_PREFIX}_#{plural_name}" + klass_name = "ITunes#{Utils.to_class_name(name)}" + plural_klass_name = "ITunes#{Utils.to_class_name(plural_name)}" + def_elements_class_accessor(klass, full_name, full_plural_name, + klass_name, plural_klass_name, + recommended_attribute_name) + end + end + + def def_yes_other_accessor(klass, full_name) + klass.def_other_element(full_name) + klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1) + def #{full_name}? + Utils::YesOther.parse(@#{full_name}) + end + EOC + end + + def def_yes_clean_other_accessor(klass, full_name) + klass.def_other_element(full_name) + klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1) + def #{full_name}? + Utils::YesCleanOther.parse(#{full_name}) + end + EOC + end + + def def_csv_accessor(klass, full_name) + klass.def_csv_element(full_name) + end + + def def_elements_class_accessor(klass, full_name, full_plural_name, + klass_name, plural_klass_name, + recommended_attribute_name=nil) + if recommended_attribute_name + klass.def_classed_elements(full_name, recommended_attribute_name, + plural_klass_name, full_plural_name) + else + klass.def_classed_element(full_plural_name, plural_klass_name) + end + klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1) + def new_#{full_name}(text=nil) + #{full_name} = @#{full_plural_name}.new_#{full_name} + #{full_name}.text = text + if block_given? + yield #{full_name} + else + #{full_name} + end + end + EOC + end + end + + module ITunesChannelModel + extend ITunesBaseModel + + class << self + def append_features(klass) + super + + ::RSS::ITunesChannelModel::ELEMENT_INFOS.each do |name, type, *args| + def_class_accessor(klass, name, type, *args) + end + end + end + + class ITunesCategoriesBase < Base + def_array_element("category", "itunes_categories", + "ITunesCategory") + class ITunesCategoryBase < Base + attr_accessor :text + add_need_initialize_variable("text") + def_array_element("category", "itunes_categories", + "ITunesCategory") + + def have_required_values? + text + end + + alias_method :to_feed_for_categories, :to_feed + def to_feed(feed, current) + if text and current.respond_to?(:itunes_category) + new_item = current.class::ITunesCategory.new(text) + to_feed_for_categories(feed, new_item) + current.itunes_categories << new_item + end + end + end + end + + class ITunesImageBase < Base + add_need_initialize_variable("href") + attr_accessor("href") + + def to_feed(feed, current) + if @href and current.respond_to?(:itunes_image) + current.itunes_image ||= current.class::ITunesImage.new + current.itunes_image.href = @href + end + end + end + + class ITunesOwnerBase < Base + %w(itunes_name itunes_email).each do |name| + add_need_initialize_variable(name) + attr_accessor(name) + end + + def to_feed(feed, current) + if current.respond_to?(:itunes_owner=) + _not_set_required_variables = not_set_required_variables + if (required_variable_names - _not_set_required_variables).empty? + return + end + + unless have_required_values? + raise NotSetError.new("maker.channel.itunes_owner", + _not_set_required_variables) + end + current.itunes_owner ||= current.class::ITunesOwner.new + current.itunes_owner.itunes_name = @itunes_name + current.itunes_owner.itunes_email = @itunes_email + end + end + + private + def required_variable_names + %w(itunes_name itunes_email) + end + end + end + + module ITunesItemModel + extend ITunesBaseModel + + class << self + def append_features(klass) + super + + ::RSS::ITunesItemModel::ELEMENT_INFOS.each do |name, type, *args| + def_class_accessor(klass, name, type, *args) + end + end + end + + class ITunesDurationBase < Base + attr_reader :content + add_need_initialize_variable("content") + + %w(hour minute second).each do |name| + attr_reader(name) + add_need_initialize_variable(name, '0') + end + + def content=(content) + if content.nil? + @hour, @minute, @second, @content = nil + else + @hour, @minute, @second = + ::RSS::ITunesItemModel::ITunesDuration.parse(content) + @content = content + end + end + + def hour=(hour) + @hour = Integer(hour) + update_content + end + + def minute=(minute) + @minute = Integer(minute) + update_content + end + + def second=(second) + @second = Integer(second) + update_content + end + + def to_feed(feed, current) + if @content and current.respond_to?(:itunes_duration=) + current.itunes_duration ||= current.class::ITunesDuration.new + current.itunes_duration.content = @content + end + end + + private + def update_content + components = [@hour, @minute, @second] + @content = + ::RSS::ITunesItemModel::ITunesDuration.construct(*components) + end + end + end + + class ChannelBase + include Maker::ITunesChannelModel + class ITunesCategories < ITunesCategoriesBase + class ITunesCategory < ITunesCategoryBase + ITunesCategory = self + end + end + + class ITunesImage < ITunesImageBase; end + class ITunesOwner < ITunesOwnerBase; end + end + + class ItemsBase + class ItemBase + include Maker::ITunesItemModel + class ITunesDuration < ITunesDurationBase; end + end + end + end +end diff --git a/lib/rss/maker/slash.rb b/lib/rss/maker/slash.rb new file mode 100644 index 0000000000..27adef3832 --- /dev/null +++ b/lib/rss/maker/slash.rb @@ -0,0 +1,33 @@ +require 'rss/slash' +require 'rss/maker/1.0' + +module RSS + module Maker + module SlashModel + def self.append_features(klass) + super + + ::RSS::SlashModel::ELEMENT_INFOS.each do |name, type| + full_name = "#{RSS::SLASH_PREFIX}_#{name}" + case type + when :csv_integer + klass.def_csv_element(full_name, :integer) + else + klass.def_other_element(full_name) + end + end + + klass.module_eval do + alias_method(:slash_hit_parades, :slash_hit_parade) + alias_method(:slash_hit_parades=, :slash_hit_parade=) + end + end + end + + class ItemsBase + class ItemBase + include SlashModel + end + end + end +end diff --git a/lib/rss/maker/syndication.rb b/lib/rss/maker/syndication.rb index 3717086257..b81230457c 100644 --- a/lib/rss/maker/syndication.rb +++ b/lib/rss/maker/syndication.rb @@ -7,17 +7,8 @@ module RSS def self.append_features(klass) super - ::RSS::SyndicationModel::ELEMENTS.each do |element| - klass.add_need_initialize_variable(element) - klass.add_other_element(element) - klass.module_eval(<<-EOC, __FILE__, __LINE__+1) - attr_accessor :#{element} - def setup_#{element}(rss, current) - if #{element} and current.respond_to?(:#{element}=) - current.#{element} = @#{element} if @#{element} - end - end - EOC + ::RSS::SyndicationModel::ELEMENTS.each do |name| + klass.def_other_element(name) end end end diff --git a/lib/rss/maker/taxonomy.rb b/lib/rss/maker/taxonomy.rb index f272996581..798b239df9 100644 --- a/lib/rss/maker/taxonomy.rb +++ b/lib/rss/maker/taxonomy.rb @@ -8,24 +8,14 @@ module RSS def self.append_features(klass) super - klass.add_need_initialize_variable("taxo_topics", "make_taxo_topics") - klass.add_other_element("taxo_topics") - klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1) - attr_reader :taxo_topics - def make_taxo_topics - self.class::TaxonomyTopics.new(@maker) - end - - def setup_taxo_topics(rss, current) - @taxo_topics.to_rss(rss, current) - end -EOC + klass.def_classed_element("#{RSS::TAXO_PREFIX}_topics", + "TaxonomyTopics") 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 @@ -39,11 +29,10 @@ EOC EOC end - class TaxonomyTopicsBase - include Base - + class TaxonomyTopicsBase < Base attr_reader :resources - def_array_element("resources") + def_array_element("resource") + remove_method :new_resource end end @@ -51,77 +40,35 @@ EOC def self.append_features(klass) super - klass.add_need_initialize_variable("taxo_topics", "make_taxo_topics") - klass.add_other_element("taxo_topics") - klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1) - attr_reader :taxo_topics - def make_taxo_topics - self.class::TaxonomyTopics.new(@maker) - end - - def setup_taxo_topics(rss, current) - @taxo_topics.to_rss(rss, current) - end - - def taxo_topic - @taxo_topics[0] and @taxo_topics[0].value - end - - def taxo_topic=(new_value) - @taxo_topic[0] = self.class::TaxonomyTopic.new(self) - @taxo_topic[0].value = new_value - end -EOC + class_name = "TaxonomyTopics" + klass.def_classed_elements("#{TAXO_PREFIX}_topic", "value", class_name) 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 end - class TaxonomyTopicsBase - include Base - - def_array_element("taxo_topics") - - def new_taxo_topic - taxo_topic = self.class::TaxonomyTopic.new(self) - @taxo_topics << taxo_topic - if block_given? - yield taxo_topic - else - taxo_topic - end - end + class TaxonomyTopicsBase < Base + def_array_element("taxo_topic", nil, "TaxonomyTopic") - def to_rss(rss, current) - @taxo_topics.each do |taxo_topic| - taxo_topic.to_rss(rss, current) - end - end - - class TaxonomyTopicBase - include Base + class TaxonomyTopicBase < Base include DublinCoreModel include TaxonomyTopicsModel @@ -151,32 +98,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 4ae6164f68..278fe53ebe 100644 --- a/lib/rss/maker/trackback.rb +++ b/lib/rss/maker/trackback.rb @@ -8,57 +8,15 @@ module RSS def self.append_features(klass) super - name = "#{RSS::TRACKBACK_PREFIX}_ping" - klass.add_need_initialize_variable(name) - klass.add_other_element(name) - klass.module_eval(<<-EOC, __FILE__, __LINE__+1) - attr_accessor :#{name} - def setup_#{name}(rss, current) - if #{name} and current.respond_to?(:#{name}=) - current.#{name} = #{name} - end - end - EOC - - 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) - attr_accessor :#{name} - def make_#{name} - self.class::TrackBackAbouts.new(self) - end - - def setup_#{name}(rss, current) - @#{name}.to_rss(rss, current) - end - EOC + klass.def_other_element("#{RSS::TRACKBACK_PREFIX}_ping") + klass.def_classed_elements("#{RSS::TRACKBACK_PREFIX}_about", "value", + "TrackBackAbouts") end - class TrackBackAboutsBase - include Base - - def_array_element("abouts") - - def new_about - about = self.class::TrackBackAbout.new(@maker) - @abouts << about - if block_given? - yield about - else - about - end - end - - def to_rss(rss, current) - @abouts.each do |about| - about.to_rss(rss, current) - end - end - - class TrackBackAboutBase - include Base + class TrackBackAboutsBase < Base + def_array_element("about", nil, "TrackBackAbout") + class TrackBackAboutBase < Base attr_accessor :value add_need_initialize_variable("value") @@ -66,65 +24,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 033bc123aa..5edcf21e17 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 @@ -118,7 +119,7 @@ module RSS return rss if rss.is_a?(::URI::Generic) begin - URI(rss) + ::URI.parse(rss) rescue ::URI::Error rss end @@ -173,24 +174,28 @@ module RSS class << self - @@setters = {} + @@accessor_bases = {} @@registered_uris = {} @@class_names = {} # return the setter for the uri, tag_name pair, or nil. 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 # return the tag_names for setters associated with uri def available_tags(uri) begin - @@setters[uri].keys + @@accessor_bases[uri].keys rescue NameError [] end @@ -223,8 +228,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 @@ -233,35 +238,31 @@ module RSS end private - # set the setter for the uri, tag_name pair - def install_setter(uri, tag_name, setter) - @@setters[uri] ||= {} - @@setters[uri][tag_name] = setter + # set the accessor for the uri, tag_name pair + def install_accessor_base(uri, tag_name, accessor_base) + @@accessor_bases[uri] ||= {} + @@accessor_bases[uri][tag_name] = accessor_base.chomp("=") end - def def_get_text_element(uri, name, file, line) - register_uri(uri, name) - unless private_instance_methods(false).include?("start_#{name}") - module_eval(<<-EOT, file, line) - def start_#{name}(name, prefix, attrs, ns) + def def_get_text_element(uri, element_name, file, line) + register_uri(uri, element_name) + method_name = "start_#{element_name}" + unless private_method_defined?(method_name) + define_method(method_name) do |name, prefix, attrs, ns| uri = _ns(ns, prefix) - if self.class.uri_registered?(uri, #{name.inspect}) + if self.class.uri_registered?(uri, element_name) start_get_text_element(name, prefix, ns, uri) else start_else_element(name, prefix, attrs, ns) end end - EOT - __send__("private", "start_#{name}") + private(method_name) end end - end - end module ListenerMixin - attr_reader :rss attr_accessor :ignore_unknown_element @@ -271,13 +272,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 # set instance vars for version, encoding, standalone @@ -289,7 +293,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 @@ -311,10 +315,39 @@ module RSS prefix, local = split_name(name) @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 @@ -331,7 +364,11 @@ 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 @@ -354,7 +391,8 @@ module RSS def start_else_element(local, prefix, attrs, ns) class_name = self.class.class_name(_ns(ns, prefix), local) current_class = @last_element.class - if current_class.constants.include?(class_name) + if current_class.const_defined?(class_name) or + current_class.constants.include?(class_name) next_class = current_class.const_get(class_name) start_have_something_element(local, prefix, attrs, ns, next_class) else @@ -377,19 +415,26 @@ module RSS end def check_ns(tag_name, prefix, ns, require_uri) - if @do_validate - if _ns(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) if @last_element.respond_to?(setter) + if @do_validate + getter = self.class.getter(required_uri, tag_name) + if @last_element.__send__(getter) + raise TooMuchTagError.new(tag_name, @last_element.tag_name) + end + end @last_element.__send__(setter, text.to_s) else if @do_validate and !@ignore_unknown_element @@ -397,7 +442,8 @@ module RSS @last_element.tag_name) end end - } + end + @proc_stack.push(pr) end def start_have_something_element(tag_name, prefix, attrs, ns, klass) @@ -438,16 +484,32 @@ module RSS previous = @last_element next_element = klass.new(@do_validate, attributes) - previous.instance_eval {set_next_element(tag_name, next_element)} + previous.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? + 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/prelude.rb b/lib/rss/prelude.rb new file mode 100644 index 0000000000..8d6f70657e --- /dev/null +++ b/lib/rss/prelude.rb @@ -0,0 +1,25 @@ + +# Mutex + +class Mutex + def synchronize + self.lock + begin + yield + ensure + self.unlock + end + end +end + +# Thread + +class Thread + MUTEX_FOR_THREAD_EXCLUSIVE = Mutex.new + def self.exclusive + MUTEX_FOR_THREAD_EXCLUSIVE.synchronize{ + yield + } + end +end + diff --git a/lib/rss/rss.rb b/lib/rss/rss.rb index a06985af94..d2e226c0fe 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 @@ -25,8 +33,15 @@ class Time end end - unless instance_methods.include?("w3cdtf") - alias w3cdtf iso8601 + unless method_defined?(:w3cdtf) + def w3cdtf + if usec.zero? + fraction_digits = 0 + else + fraction_digits = Math.log10(usec.to_s.sub(/0*$/, '').to_i).floor + 1 + end + xmlschema(fraction_digits) + end end end @@ -37,7 +52,7 @@ require "rss/xml-stylesheet" module RSS - VERSION = "0.1.6" + VERSION = "0.2.0" URI = "http://purl.org/rss/1.0/" @@ -136,17 +151,26 @@ module RSS super("required variables of #{@name} are not set: #{@variables.join(', ')}") end end - - module BaseModel + class UnsupportedMakerVersionError < Error + attr_reader :version + def initialize(version) + @version = version + super("Maker doesn't support version: #{@version}") + end + end + + module BaseModel include Utils - def install_have_child_element(tag_name, uri, occurs, name=nil) + 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} @@ -164,7 +188,7 @@ EOC 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) + install_model(tag_name, uri, occurs, plural_name, true) def_children_accessor(name, plural_name) install_element(name, "s") do |n, elem_name| @@ -179,20 +203,26 @@ EOC end end - def install_text_element(tag_name, uri, occurs, name=nil, type=nil, disp_name=nil) + 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 + def_corresponded_attr_writer(name, type, disp_name) + def_corresponded_attr_reader(name, type || :convert) install_element(name) do |n, elem_name| <<-EOC - if @#{n} + if respond_to?(:#{n}_content) + content = #{n}_content + else + content = @#{n} + end + if content rv = "\#{indent}<#{elem_name}>" - value = html_escape(@#{n}) + value = html_escape(content) if need_convert rv << convert(value) else @@ -252,26 +282,112 @@ EOC 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 @converter - @converter.convert(@#{attr}) + if @#{attr} + #{attr}_without_inherit + elsif @parent + @parent.#{attr} else - @#{attr} + nil end end EOC end end + def uri_convert_attr_reader(*attrs) + attrs.each do |attr| + attr = attr.id2name if attr.kind_of?(Integer) + module_eval(<<-EOC, *get_file_and_line_from_caller(2)) + def #{attr}_without_base + convert(@#{attr}) + end + + def #{attr} + value = #{attr}_without_base + return nil if value.nil? + if /\\A[a-z][a-z0-9+.\\-]*:/i =~ value + value + else + "\#{base}\#{value}" + end + end +EOC + end + end + + def convert_attr_reader(*attrs) + attrs.each do |attr| + attr = attr.id2name if attr.kind_of?(Integer) + module_eval(<<-EOC, *get_file_and_line_from_caller(2)) + def #{attr} + convert(@#{attr}) + end +EOC + end + end + + def yes_clean_other_attr_reader(*attrs) + attrs.each do |attr| + attr = attr.id2name if attr.kind_of?(Integer) + module_eval(<<-EOC, __FILE__, __LINE__ + 1) + attr_reader(:#{attr}) + def #{attr}? + YesCleanOther.parse(@#{attr}) + end + EOC + end + end + + def yes_other_attr_reader(*attrs) + attrs.each do |attr| + attr = attr.id2name if attr.kind_of?(Integer) + module_eval(<<-EOC, __FILE__, __LINE__ + 1) + attr_reader(:#{attr}) + def #{attr}? + Utils::YesOther.parse(@#{attr}) + end + EOC + end + end + + def csv_attr_reader(*attrs) + separator = nil + if attrs.last.is_a?(Hash) + options = attrs.pop + separator = options[:separator] + end + separator ||= ", " + attrs.each do |attr| + attr = attr.id2name if attr.kind_of?(Integer) + module_eval(<<-EOC, __FILE__, __LINE__ + 1) + attr_reader(:#{attr}) + def #{attr}_content + if @#{attr}.nil? + @#{attr} + else + @#{attr}.join(#{separator.dump}) + end + end + EOC + end + end + def date_writer(name, type, disp_name=name) module_eval(<<-EOC, *get_file_and_line_from_caller(2)) def #{name}=(new_value) - if new_value.nil? 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 @@ -283,7 +399,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 @@ -364,6 +482,68 @@ 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 yes_clean_other_writer(name, disp_name=name) + module_eval(<<-EOC, __FILE__, __LINE__ + 1) + def #{name}=(value) + value = (value ? "yes" : "no") if [true, false].include?(value) + @#{name} = value + end + EOC + end + + def yes_other_writer(name, disp_name=name) + module_eval(<<-EOC, __FILE__, __LINE__ + 1) + def #{name}=(new_value) + if [true, false].include?(new_value) + new_value = new_value ? "yes" : "no" + end + @#{name} = new_value + end + EOC + end + + def csv_writer(name, disp_name=name) + module_eval(<<-EOC, __FILE__, __LINE__ + 1) + def #{name}=(new_value) + @#{name} = Utils::CSV.parse(new_value) + end + EOC + end + + def csv_integer_writer(name, disp_name=name) + module_eval(<<-EOC, __FILE__, __LINE__ + 1) + def #{name}=(new_value) + @#{name} = Utils::CSV.parse(new_value) {|v| Integer(v)} + end + EOC + end + def def_children_accessor(accessor_name, plural_name) module_eval(<<-EOC, *get_file_and_line_from_caller(2)) def #{plural_name} @@ -379,10 +559,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 @@ -394,10 +576,61 @@ EOC end end - class Element + module SetupMaker + def setup_maker(maker) + target = maker_target(maker) + unless target.nil? + setup_maker_attributes(target) + setup_maker_element(target) + setup_maker_elements(target) + end + end + + private + def maker_target(maker) + nil + end + + def setup_maker_attributes(target) + end + + def setup_maker_element(target) + self.class.need_initialize_variables.each do |var| + value = __send__(var) + next if value.nil? + if value.respond_to?("setup_maker") and + !not_need_to_call_setup_maker_variables.include?(var) + value.setup_maker(target) + else + setter = "#{var}=" + if target.respond_to?(setter) + target.__send__(setter, value) + end + end + end + end + + def not_need_to_call_setup_maker_variables + [] + end + + def setup_maker_elements(parent) + self.class.have_children_elements.each do |name, plural_name| + if parent.respond_to?(plural_name) + target = parent.__send__(plural_name) + __send__(plural_name).each do |elem| + elem.setup_maker(target) + end + end + end + end + end + class Element extend BaseModel include Utils + extend Utils::InheritedReader + include SetupMaker INDENT = " " @@ -408,32 +641,34 @@ EOC TO_ELEMENT_METHODS = [] NEED_INITIALIZE_VARIABLES = [] PLURAL_FORMS = {} - - class << self + class << self def must_call_validators - MUST_CALL_VALIDATORS + inherited_hash_reader("MUST_CALL_VALIDATORS") end def models - MODELS + inherited_array_reader("MODELS") end def get_attributes - GET_ATTRIBUTES + inherited_array_reader("GET_ATTRIBUTES") end def have_children_elements - HAVE_CHILDREN_ELEMENTS + inherited_array_reader("HAVE_CHILDREN_ELEMENTS") end def to_element_methods - TO_ELEMENT_METHODS + inherited_array_reader("TO_ELEMENT_METHODS") end def need_initialize_variables - NEED_INITIALIZE_VARIABLES + inherited_array_reader("NEED_INITIALIZE_VARIABLES") end def plural_forms - PLURAL_FORMS + inherited_hash_reader("PLURAL_FORMS") + end + + def inherited_base + ::RSS::Element end - def inherited(klass) klass.const_set("MUST_CALL_VALIDATORS", {}) klass.const_set("MODELS", []) @@ -443,105 +678,112 @@ EOC klass.const_set("NEED_INITIALIZE_VARIABLES", []) klass.const_set("PLURAL_FORMS", {}) - klass.module_eval(<<-EOC) - public - - @tag_name = name.split(/::/).last - @tag_name[0,1] = @tag_name[0,1].downcase - @have_content = false + tag_name = klass.name.split(/::/).last + tag_name[0, 1] = tag_name[0, 1].downcase + klass.instance_variable_set("@tag_name", tag_name) + klass.instance_variable_set("@have_content", false) + end - def self.must_call_validators - super.merge(MUST_CALL_VALIDATORS) - end - def self.models - MODELS + super - end - def self.get_attributes - GET_ATTRIBUTES + super - end - 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 + def install_must_call_validator(prefix, uri) + self::MUST_CALL_VALIDATORS[uri] = prefix + end + + def install_model(tag, uri, occurs=nil, getter=nil, plural=false) + getter ||= tag + if m = self::MODELS.find {|t, u, o, g, p| t == tag and u == uri} + m[2] = occurs + else + self::MODELS << [tag, uri, occurs, getter, plural] end - def self.plural_forms - super.merge(PLURAL_FORMS) + end + + def install_get_attribute(name, uri, required=true, + type=nil, disp_name=nil, + element_name=nil) + disp_name ||= name + element_name ||= name + writer_type, reader_type = type + def_corresponded_attr_writer name, writer_type, disp_name + def_corresponded_attr_reader name, reader_type + if type == :boolean and /^is/ =~ name + alias_method "#{$POSTMATCH}?", name end + self::GET_ATTRIBUTES << [name, uri, required, element_name] + add_need_initialize_variable(disp_name) + end - - def self.install_must_call_validator(prefix, uri) - MUST_CALL_VALIDATORS[uri] = prefix - end - - 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 - MODELS << [tag, uri, occurs, getter] - end + def def_corresponded_attr_writer(name, type=nil, disp_name=nil) + disp_name ||= name + case type + when :integer + integer_writer name, disp_name + when :positive_integer + positive_integer_writer name, disp_name + when :boolean + boolean_writer name, disp_name + when :w3cdtf, :rfc822, :rfc2822 + date_writer name, type, disp_name + when :text_type + text_type_writer name, disp_name + when :content + content_writer name, disp_name + when :yes_clean_other + yes_clean_other_writer name, disp_name + when :yes_other + yes_other_writer name, disp_name + when :csv + csv_writer name + when :csv_integer + csv_integer_writer name + else + attr_writer name end + end - def self.install_get_attribute(name, uri, required=true, - type=nil, disp_name=nil, - element_name=nil) - disp_name ||= name - element_name ||= name - def_corresponded_attr_writer name, type, disp_name + def def_corresponded_attr_reader(name, type=nil) + case type + when :inherit + inherit_convert_attr_reader name + when :uri + uri_convert_attr_reader name + when :yes_clean_other + yes_clean_other_attr_reader name + when :yes_other + yes_other_attr_reader name + when :csv + csv_attr_reader name + when :csv_integer + csv_attr_reader name, :separator => "," + else convert_attr_reader name - if type == :boolean and /^is/ =~ name - alias_method "\#{$POSTMATCH}?", name - end - 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) - case type - when :integer - integer_writer name, disp_name - when :positive_integer - positive_integer_writer name, disp_name - when :boolean - boolean_writer name, disp_name - when :w3cdtf, :rfc822, :rfc2822 - date_writer name, type, disp_name - else - attr_writer name - end end + end - def self.content_setup(type=nil) - def_corresponded_attr_writer "content", type - convert_attr_reader :content - @have_content = true - end + def content_setup(type=nil, disp_name=nil) + writer_type, reader_type = type + def_corresponded_attr_writer :content, writer_type, disp_name + def_corresponded_attr_reader :content, reader_type + @have_content = true + end - def self.have_content? - @have_content - end + def have_content? + @have_content + end - def self.add_have_children_element(variable_name, plural_name) - HAVE_CHILDREN_ELEMENTS << [variable_name, plural_name] - end - - def self.add_to_element_method(method_name) - TO_ELEMENT_METHODS << method_name - end + def add_have_children_element(variable_name, plural_name) + self::HAVE_CHILDREN_ELEMENTS << [variable_name, plural_name] + end - def self.add_need_initialize_variable(variable_name) - NEED_INITIALIZE_VARIABLES << variable_name - end - - def self.add_plural_form(singular, plural) - PLURAL_FORMS[singular] = plural - end - - EOC + def add_to_element_method(method_name) + self::TO_ELEMENT_METHODS << method_name + end + + def add_need_initialize_variable(variable_name) + self::NEED_INITIALIZE_VARIABLES << variable_name + end + + def add_plural_form(singular, plural) + self::PLURAL_FORMS[singular] = plural end def required_prefix @@ -551,7 +793,11 @@ EOC def required_uri "" end - + + def need_parent? + false + end + def install_ns(prefix, uri) if self::NSPOOL.has_key?(prefix) raise OverlappedPrefixError.new(prefix) @@ -564,12 +810,16 @@ EOC end end - attr_accessor :do_validate + attr_accessor :parent, :do_validate - def initialize(do_validate=true, attrs={}) + def initialize(do_validate=true, attrs=nil) + @parent = nil @converter = nil + if attrs.nil? and (do_validate.is_a?(Hash) or do_validate.is_a?(Array)) + do_validate, attrs = true, do_validate + end @do_validate = do_validate - initialize_variables(attrs) + initialize_variables(attrs || {}) end def tag_name @@ -598,10 +848,21 @@ EOC value end end - + + def valid?(ignore_unknown_element=true) + validate(ignore_unknown_element) + true + rescue RSS::Error + false + end + def validate(ignore_unknown_element=true) + do_validate = @do_validate + @do_validate = true validate_attribute __validate(ignore_unknown_element) + ensure + @do_validate = do_validate end def validate_for_stream(tags, ignore_unknown_element=true) @@ -609,20 +870,15 @@ EOC __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) - end - end - def to_s(need_convert=true, indent='') if self.class.have_content? - return "" unless @content + return "" if !empty_content? and !content_is_set? rv = tag(indent) do |next_indent| - h(@content) + if empty_content? + "" + else + xmled_content + end end else rv = tag(indent) do |next_indent| @@ -635,6 +891,44 @@ EOC rv end + def have_xml_content? + false + end + + def need_base64_encode? + false + end + + def set_next_element(tag_name, next_element) + klass = next_element.class + prefix = "" + prefix << "#{klass.required_prefix}_" if klass.required_prefix + key = "#{prefix}#{tag_name.gsub(/-/, '_')}" + if self.class.plural_forms.has_key?(key) + ary = __send__("#{self.class.plural_forms[key]}") + ary << next_element + else + __send__("#{key}=", next_element) + end + end + + protected + def have_required_elements? + self.class::MODELS.all? do |tag, uri, occurs, getter| + if occurs.nil? or occurs == "+" + child = __send__(getter) + if child.is_a?(Array) + children = child + children.any? {|c| c.have_required_elements?} + else + !child.to_s.empty? + end + else + true + end + end + end + private def initialize_variables(attrs) normalized_attrs = {} @@ -646,16 +940,16 @@ EOC if value __send__("#{variable_name}=", value) else - instance_eval("@#{variable_name} = nil") + instance_variable_set("@#{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 self.class.have_children_elements.each do |variable_name, plural_name| - instance_eval("@#{variable_name} = []") + instance_variable_set("@#{variable_name}", []) end end @@ -665,8 +959,10 @@ EOC attrs = collect_attrs return "" if attrs.nil? + return "" unless have_required_elements? + attrs.update(additional_attrs) - start_tag = make_start_tag(indent, next_indent, attrs) + start_tag = make_start_tag(indent, next_indent, attrs.dup) if block content = block.call(next_indent) @@ -681,6 +977,7 @@ EOC else content = content.reject{|x| x.empty?} if content.empty? + return "" if attrs.empty? end_tag = "/>" else start_tag << ">\n" @@ -722,56 +1019,6 @@ EOC '' 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 - end - - def set_next_element(tag_name, next_element) - klass = next_element.class - prefix = "" - prefix << "#{klass.required_prefix}_" if klass.required_prefix - key = "#{prefix}#{tag_name}" - if self.class.plural_forms.has_key?(key) - ary = __send__("#{self.class.plural_forms[key]}") - ary << next_element - else - __send__("#{prefix}#{tag_name}=", next_element) - end - end - def children rv = [] self.class.models.each do |name, uri, occurs, getter| @@ -787,10 +1034,10 @@ EOC def _tags rv = [] - self.class.models.each do |name, uri, occurs, getter| + self.class.models.each do |name, uri, occurs, getter, plural| value = __send__(getter) next if value.nil? - if value.is_a?(Array) + if plural and value.is_a?(Array) rv.concat([[uri, name]] * value.size) else rv << [uri, name] @@ -817,7 +1064,7 @@ EOC must_call_validators.each do |uri, prefix| _validate(ignore_unknown_element, tags[uri], uri) meth = "#{prefix}_validate" - if respond_to?(meth, true) + if !prefix.empty? and respond_to?(meth, true) __send__(meth, ignore_unknown_element, tags[uri], uri) end end @@ -825,9 +1072,11 @@ EOC 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 @@ -843,11 +1092,12 @@ EOC tags = tags.sort_by {|x| element_names.index(x) || tags_size} end + _tags = tags.dup if tags models.each_with_index do |model, i| name, model_uri, occurs, getter = model if DEBUG - p "before" + p "before" p tags p model end @@ -933,6 +1183,27 @@ 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 end module RootElementMixin @@ -940,16 +1211,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) @@ -964,25 +1242,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_xml(version=nil, &block) - if version.nil? or version == @rss_version + 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 - RSS::Maker.make(version) do |maker| - setup_maker(maker) - block.call(maker) if block - end.to_s + to_feed(type, &block).to_s end end private + def same_feed_type?(type) + if /^(atom|rss)?(\d+\.\d+)?(?::(.+))?$/i =~ type + feed_type = ($1 || @feed_type).downcase + feed_version = $2 || @feed_version + feed_subtype = $3 || @feed_subtype + [feed_type, feed_version, feed_subtype] == feed_info + else + false + end + end + def tag(indent, attrs={}, &block) - rv = xmldecl + xml_stylesheet_pi - rv << super(indent, ns_declarations.merge(attrs), &block) - rv + rv = super(indent, ns_declarations.merge(attrs), &block) + return rv if rv.empty? + "#{xmldecl}#{xml_stylesheet_pi}#{rv}" end def xmldecl @@ -1003,13 +1304,9 @@ EOC 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/slash.rb b/lib/rss/slash.rb new file mode 100644 index 0000000000..f102413b46 --- /dev/null +++ b/lib/rss/slash.rb @@ -0,0 +1,49 @@ +require 'rss/1.0' + +module RSS + SLASH_PREFIX = 'slash' + SLASH_URI = "http://purl.org/rss/1.0/modules/slash/" + + RDF.install_ns(SLASH_PREFIX, SLASH_URI) + + module SlashModel + extend BaseModel + + ELEMENT_INFOS = \ + [ + ["section"], + ["department"], + ["comments", :positive_integer], + ["hit_parade", :csv_integer], + ] + + class << self + def append_features(klass) + super + + return if klass.instance_of?(Module) + klass.install_must_call_validator(SLASH_PREFIX, SLASH_URI) + ELEMENT_INFOS.each do |name, type, *additional_infos| + full_name = "#{SLASH_PREFIX}_#{name}" + klass.install_text_element(full_name, SLASH_URI, "?", + full_name, type, name) + end + + klass.module_eval do + alias_method(:slash_hit_parades, :slash_hit_parade) + undef_method(:slash_hit_parade) + alias_method(:slash_hit_parade, :slash_hit_parade_content) + end + end + end + end + + class RDF + class Item; include SlashModel; end + end + + SlashModel::ELEMENT_INFOS.each do |name, type| + accessor_base = "#{SLASH_PREFIX}_#{name}" + BaseListener.install_get_text_element(SLASH_URI, name, accessor_base) + end +end diff --git a/lib/rss/syndication.rb b/lib/rss/syndication.rb index 93d35c89a7..3eb15429f6 100644 --- a/lib/rss/syndication.rb +++ b/lib/rss/syndication.rb @@ -29,16 +29,19 @@ module RSS %w(updateBase).each do |name| install_date_element(name, SY_URI, "?", - "#{SY_PREFIX}_#{name}", 'w3cdtf', name) + "#{SY_PREFIX}_#{name}", 'w3cdtf', + "#{SY_PREFIX}:#{name}") end + end + klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1) alias_method(:_sy_updatePeriod=, :sy_updatePeriod=) def sy_updatePeriod=(new_value) new_value = new_value.strip validate_sy_updatePeriod(new_value) if @do_validate self._sy_updatePeriod = new_value end - end + EOC end private @@ -58,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 8caa25e2a4..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 diff --git a/lib/rss/utils.rb b/lib/rss/utils.rb index b242a72292..0e4001e1f3 100644 --- a/lib/rss/utils.rb +++ b/lib/rss/utils.rb @@ -4,14 +4,16 @@ module RSS # Convert a name_with_underscores to CamelCase. def to_class_name(name) - name.split(/_/).collect do |part| + 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] + line = line.to_i + line += 1 if i.zero? + [file, line] end # escape '&', '"', '<' and '>' for use in HTML. @@ -33,5 +35,77 @@ module RSS def element_initialize_arguments?(args) [true, false].include?(args[0]) and args[1].is_a?(Hash) end + + module YesCleanOther + module_function + def parse(value) + if [true, false, nil].include?(value) + value + else + case value.to_s + when /\Ayes\z/i + true + when /\Aclean\z/i + false + else + nil + end + end + end + end + + module YesOther + module_function + def parse(value) + if [true, false].include?(value) + value + else + /\Ayes\z/i.match(value.to_s) ? true : false + end + end + end + + module CSV + module_function + def parse(value, &block) + if value.is_a?(String) + value = value.strip.split(/\s*,\s*/) + value = value.collect(&block) if block_given? + value + else + value + end + end + end + + module InheritedReader + def inherited_reader(constant_name) + base_class = inherited_base + result = base_class.const_get(constant_name) + found_base_class = false + ancestors.reverse_each do |klass| + if found_base_class + if klass.const_defined?(constant_name) + result = yield(result, klass.const_get(constant_name)) + end + else + found_base_class = klass == base_class + end + end + result + end + + def inherited_array_reader(constant_name) + inherited_reader(constant_name) do |result, current| + current + result + end + end + + def inherited_hash_reader(constant_name) + inherited_reader(constant_name) do |result, current| + result.merge(current) + end + end + end end end diff --git a/lib/rss/xml-stylesheet.rb b/lib/rss/xml-stylesheet.rb index 66e3161dd0..559d6bcd56 100644 --- a/lib/rss/xml-stylesheet.rb +++ b/lib/rss/xml-stylesheet.rb @@ -35,6 +35,10 @@ module RSS attr_accessor(*ATTRIBUTES) attr_accessor(:do_validate) def initialize(*attrs) + if attrs.size == 1 and + (attrs.first.is_a?(Hash) or attrs.first.is_a?(Array)) + attrs = attrs.first + end @do_validate = true ATTRIBUTES.each do |attr| __send__("#{attr}=", nil) 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 diff --git a/sample/rss/blend.rb b/sample/rss/blend.rb index 2fbd6efed6..351f6f373f 100755 --- a/sample/rss/blend.rb +++ b/sample/rss/blend.rb @@ -64,6 +64,12 @@ rss = RSS::Maker.make("1.0") do |maker| item.setup_maker(maker.items) end end + + maker.items.each do |item| + item.title ||= "UNKNOWN" + item.link ||= "UNKNOWN" + end + maker.items.do_sort = true maker.items.max_size = 15 end diff --git a/sample/rss/convert.rb b/sample/rss/convert.rb index 394b13e8eb..e6bff4c623 100755 --- a/sample/rss/convert.rb +++ b/sample/rss/convert.rb @@ -23,7 +23,7 @@ ARGV.each do |fname| when '-v' verbose = true next - when /^-t(0\.91|1\.0|2\.0)$/ + when /^-t(0\.91|1\.0|2\.0|atom)$/ to_version = $1 next end diff --git a/sample/rss/list_description.rb b/sample/rss/list_description.rb index bb1f9636e2..d4b98a9ac6 100644..100755 --- a/sample/rss/list_description.rb +++ b/sample/rss/list_description.rb @@ -56,9 +56,18 @@ ARGV.each do |fname| rescue RSS::UnknownConversionMethodError error($!) if verbose end - rss.channel.title ||= "Unknown" + + rss = rss.to_rss("1.0") do |maker| + maker.channel.about ||= maker.channel.link + maker.channel.description ||= "No description" + maker.items.each do |item| + item.title ||= "No title" + item.link ||= "UNKNOWN" + end + end + next if rss.nil? + rss.items.each do |item| - item.title ||= "Unknown" channels[rss.channel.title] ||= [] channels[rss.channel.title] << item if item.description end diff --git a/sample/rss/re_read.rb b/sample/rss/re_read.rb index c19a5099bb..ee54a18a88 100755 --- a/sample/rss/re_read.rb +++ b/sample/rss/re_read.rb @@ -29,16 +29,16 @@ ARGV.each do |fname| read = false begin rss = RSS::Parser.parse(source) - puts "Re-read valid RSS: #{fname}" + puts "Re-read valid feed: #{fname}" RSS::Parser.parse(rss.to_s) read = true rescue RSS::InvalidRSSError error($!) if verbose - ## do non validate parse for invalid RSS 1.0 + ## do non validate parse for invalid feed begin rss = RSS::Parser.parse(source, false) rescue RSS::Error - ## invalid RSS. + ## invalid feed error($!) if verbose end rescue RSS::Error @@ -46,9 +46,9 @@ ARGV.each do |fname| end if rss.nil? - puts "Invalid RSS: #{fname}" + puts "Invalid feed: #{fname}" elsif !read - puts "Re-read invalid RSS: #{fname}" + puts "Re-read invalid feed: #{fname}" begin RSS::Parser.parse(rss.to_s) rescue RSS::Error diff --git a/sample/rss/rss_recent.rb b/sample/rss/rss_recent.rb index 7821df5c7b..38b57c37fa 100644..100755 --- a/sample/rss/rss_recent.rb +++ b/sample/rss/rss_recent.rb @@ -55,16 +55,20 @@ ARGV.each do |fname| rescue RSS::UnknownConversionMethodError error($!) if verbose end - rss.items.each do |item| - if item.respond_to?(:pubDate) and item.pubDate - class << item - alias_method(:dc_date, :pubDate) - end - end - if item.respond_to?(:dc_date) and item.dc_date - items << [rss.channel, item] + + rss = rss.to_rss("1.0") do |maker| + maker.channel.about ||= maker.channel.link + maker.channel.description ||= "No description" + maker.items.each do |item| + item.title ||= "UNKNOWN" + item.link ||= "UNKNOWN" end end + next if rss.nil? + + rss.items.each do |item| + items << [rss.channel, item] if item.dc_date + end end end processing_time = Time.now - before_time diff --git a/test/rss/rss-assertions.rb b/test/rss/rss-assertions.rb index 1e926bb84d..41e6cd62c5 100644 --- a/test/rss/rss-assertions.rb +++ b/test/rss/rss-assertions.rb @@ -1,19 +1,7 @@ -module Test - module Unit - module Assertions - # For backward compatibility - unless instance_methods.include?("assert_raise") - def assert_raise(*args, &block) - assert_raises(*args, &block) - end - end - end - end -end +require 'erb' module RSS module Assertions - def assert_parse(rss, assert_method, *args) __send__("assert_#{assert_method}", *args) do ::RSS::Parser.parse(rss) @@ -104,6 +92,7 @@ module RSS flunk("Not raise NotSetError") rescue ::RSS::NotSetError => e assert_equal(name, e.name) + assert_kind_of(Array, variables) assert_equal(variables.sort, e.variables.sort) end end @@ -143,10 +132,13 @@ module RSS def assert_xml_stylesheet_pis(attrs_ary, rss=nil) _wrap_assertion do - rss ||= ::RSS::RDF.new() + if rss.nil? + rss = ::RSS::RDF.new + setup_rss10(rss) + end xss_strs = [] attrs_ary.each do |attrs| - xss = ::RSS::XMLStyleSheet.new(*attrs) + xss = ::RSS::XMLStyleSheet.new(attrs) xss_strs.push(xss.to_s) rss.xml_stylesheets.push(xss) end @@ -163,239 +155,650 @@ module RSS end end - - def assert_channel10(attrs, channel) + + def assert_atom_person(tag_name, generator) _wrap_assertion do - n_attrs = normalized_attrs(attrs) - - names = %w(about title link description) - assert_attributes(attrs, names, channel) + name = "Mark Pilgrim" + uri = "http://example.org/" + email = "f8dy@example.com" - %w(image items textinput).each do |name| - value = n_attrs[name] - if value - target = channel.__send__(name) - __send__("assert_channel10_#{name}", value, target) + assert_parse(generator.call(<<-EOA), :missing_tag, "name", tag_name) + <#{tag_name}/> +EOA + + assert_parse(generator.call(<<-EOA), :missing_tag, "name", tag_name) + <#{tag_name}> + <uri>#{uri}</uri> + <email>#{email}</email> + </#{tag_name}> +EOA + + assert_parse(generator.call(<<-EOA), :nothing_raised) + <#{tag_name}> + <name>#{name}</name> + </#{tag_name}> +EOA + + feed = RSS::Parser.parse(generator.call(<<-EOA)) + <#{tag_name}> + <name>#{name}</name> + <uri>#{uri}</uri> + <email>#{email}</email> + </#{tag_name}> +EOA + + person = yield(feed) + assert_not_nil(person) + assert_equal(name, person.name.content) + assert_equal(uri, person.uri.content) + assert_equal(email, person.email.content) + end + end + + def assert_atom_category(generator) + _wrap_assertion do + term = "Music" + scheme = "http://xmlns.com/wordnet/1.6/" + label = "music" + + missing_args = [:missing_attribute, "category", "term"] + assert_parse(generator.call(<<-EOA), *missing_args) + <category/> +EOA + + assert_parse(generator.call(<<-EOA), *missing_args) + <category scheme="#{scheme}" label="#{label}"/> +EOA + + assert_parse(generator.call(<<-EOA), :nothing_raised) + <category term="#{term}"/> +EOA + + feed = RSS::Parser.parse(generator.call(<<-EOA)) + <category term="#{term}" scheme="#{scheme}" label="#{label}"/> +EOA + + category = yield(feed) + assert_not_nil(category) + assert_equal(term, category.term) + assert_equal(scheme, category.scheme) + assert_equal(label, category.label) + end + end + + def assert_atom_link(generator) + _wrap_assertion do + href = "http://example.org/feed.atom" + rel = "self" + type = "application/atom+xml" + hreflang = "en" + title = "Atom" + length = "1024" + + assert_parse(generator.call(<<-EOA), :missing_attribute, "link", "href") + <link/> +EOA + + assert_parse(generator.call(<<-EOA), :missing_attribute, "link", "href") + <link rel="#{rel}" type="#{type}" hreflang="#{hreflang}" + title="#{title}" length="#{length}"/> +EOA + + assert_parse(generator.call(<<-EOA), :nothing_raised) + <link href="#{href}"/> +EOA + + feed = RSS::Parser.parse(generator.call(<<-EOA)) + <link href="#{href}" rel="#{rel}" type="#{type}" hreflang="#{hreflang}" + title="#{title}" length="#{length}"/> +EOA + + link = yield(feed) + assert_not_nil(link) + assert_equal(href, link.href) + assert_equal(rel, link.rel) + assert_equal(type, link.type) + assert_equal(hreflang, link.hreflang) + assert_equal(title, link.title) + assert_equal(length, link.length) + + + href = "http://example.org/index.html.ja" + parent = link.parent.tag_name + return if parent == "source" + + optional_attributes = %w(hreflang="ja" type="text/html") + 0.upto(optional_attributes.size) do |i| + combination(optional_attributes, i).each do |attributes| + attrs = attributes.join(" ") + assert_parse(generator.call(<<-EOA), :too_much_tag, "link", parent) + <link rel="alternate" #{attrs} href="#{href}"/> + <link rel="alternate" #{attrs} href="#{href}"/> +EOA end end end end - def assert_channel10_image(attrs, image) + def assert_atom_generator(generator) _wrap_assertion do - assert_attributes(attrs, %w(resource), image) + uri = "http://www.example.com/" + version = "1.0" + content = "Example Toolkit" + + assert_parse(generator.call(<<-EOA), :nothing_raised) + <generator/> +EOA + + assert_parse(generator.call(<<-EOA), :nothing_raised) + <generator uri="#{uri}" version="#{version}"/> +EOA + + feed = RSS::Parser.parse(generator.call(<<-EOA)) + <generator uri="#{uri}" version="#{version}">#{content}</generator> +EOA + + gen = yield(feed) + assert_not_nil(gen) + assert_equal(uri, gen.uri) + assert_equal(version, gen.version) + assert_equal(content, gen.content) end end - - def assert_channel10_textinput(attrs, textinput) + + def assert_atom_icon(generator) _wrap_assertion do - assert_attributes(attrs, %w(resource), textinput) + content = "http://www.example.com/example.png" + + assert_parse(generator.call(<<-EOA), :nothing_raised) + <icon/> +EOA + + feed = RSS::Parser.parse(generator.call(<<-EOA)) + <icon>#{content}</icon> +EOA + + icon = yield(feed) + assert_not_nil(icon) + assert_equal(content, icon.content) end end - def assert_channel10_items(attrs, items) + def assert_atom_text_construct(tag_name, generator) _wrap_assertion do - assert_equal(items.resources, items.Seq.lis.collect {|x| x.resource}) - items.Seq.lis.each_with_index do |li, i| - assert_attributes(attrs[i], %w(resource), li) + [nil, "text", "html"].each do |type| + attr = "" + attr = " type=\"#{type}\""if type + assert_parse(generator.call(<<-EOA), :nothing_raised) + <#{tag_name}#{attr}/> +EOA + end + + assert_parse(generator.call(<<-EOA), :missing_tag, "div", tag_name) + <#{tag_name} type="xhtml"/> +EOA + + args = ["x", Atom::URI, tag_name] + assert_parse(generator.call(<<-EOA), :not_expected_tag, *args) + <#{tag_name} type="xhtml"><x/></#{tag_name}> +EOA + + invalid_value = "invalid" + args = ["type", invalid_value] + assert_parse(generator.call(<<-EOA), :not_available_value, *args) + <#{tag_name} type="#{invalid_value}"/> +EOA + + [ + [nil, "A lot of effort went into making this effortless"], + ["text", "A lot of effort went into making this effortless"], + ["html", "A <em>lot</em> of effort went into making this effortless"], + ].each do |type, content| + attr = "" + attr = " type=\"#{type}\"" if type + feed = RSS::Parser.parse(generator.call(<<-EOA)) + <#{tag_name}#{attr}>#{h content}</#{tag_name}> +EOA + + element = yield(feed) + assert_not_nil(element) + assert_equal(type, element.type) + assert_equal(content, element.content) + end + + [false, true].each do |with_space| + xhtml_uri = "http://www.w3.org/1999/xhtml" + xhtml_content = "<div xmlns=\"#{xhtml_uri}\">abc</div>" + xhtml_element = RSS::XML::Element.new("div", nil, xhtml_uri, + {"xmlns" => xhtml_uri}, + ["abc"]) + content = xhtml_content + content = " #{content} " if with_space + feed = RSS::Parser.parse(generator.call(<<-EOA)) + <#{tag_name} type="xhtml">#{content}</#{tag_name}> +EOA + + element = yield(feed) + assert_not_nil(element) + assert_equal("xhtml", element.type) + assert_equal(xhtml_content, element.content) + assert_equal(xhtml_element, element.xhtml) end end end - def assert_image10(attrs, image) + def assert_atom_date_construct(tag_name, generator) _wrap_assertion do - names = %w(about title url link) - assert_attributes(attrs, names, image) + args = [tag_name, ""] + assert_parse(generator.call(<<-EOR), :not_available_value, *args) + <#{tag_name}/> +EOR + + [ + ["xxx", false], + ["2007", false], + ["2007/02/09", true], + ].each do |invalid_value, can_parse| + assert_not_available_value(tag_name, invalid_value) do + RSS::Parser.parse(generator.call(<<-EOR)) + <#{tag_name}>#{invalid_value}</#{tag_name}> +EOR + end + + feed = RSS::Parser.parse(generator.call(<<-EOR), false) + <#{tag_name}>#{invalid_value}</#{tag_name}> +EOR + value = yield(feed).content + if can_parse + assert_equal(Time.parse(invalid_value), value) + else + assert_nil(value) + end + end + + [ + "2003-12-13T18:30:02Z", + "2003-12-13T18:30:02.25Z", + "2003-12-13T18:30:02+01:00", + "2003-12-13T18:30:02.25+01:00", + ].each do |valid_value| + assert_parse(generator.call(<<-EOR), :nothing_raised) + <#{tag_name}>#{valid_value}</#{tag_name}> +EOR + + feed = RSS::Parser.parse(generator.call(<<-EOR)) + <#{tag_name}>#{valid_value}</#{tag_name}> +EOR + assert_equal(Time.parse(valid_value), yield(feed).content) + end end end - def assert_items10(attrs, items) + def assert_atom_logo(generator) _wrap_assertion do - names = %w(about title link description) - items.each_with_index do |item, i| - assert_attributes(attrs[i], names, item) - end + content = "http://www.example.com/example.png" + + assert_parse(generator.call(<<-EOA), :nothing_raised) + <logo/> +EOA + + feed = RSS::Parser.parse(generator.call(<<-EOA)) + <logo>#{content}</logo> +EOA + + logo = yield(feed) + assert_not_nil(logo) + assert_equal(content, logo.content) end end - def assert_textinput10(attrs, textinput) + def assert_atom_content(generator, &getter) _wrap_assertion do - names = %w(about title description name link) - assert_attributes(attrs, names, textinput) + assert_atom_content_inline_text(generator, &getter) + assert_atom_content_inline_xhtml(generator, &getter) + assert_atom_content_inline_other(generator, &getter) + assert_atom_content_out_of_line(generator, &getter) end end - - def assert_channel09(attrs, channel) + def assert_atom_content_inline_text(generator) _wrap_assertion do - n_attrs = normalized_attrs(attrs) + [nil, "text", "html"].each do |type| + content = "<content" + content << " type='#{type}'" if type - names = %w(title description link language rating - copyright pubDate lastBuildDate docs - managingEditor webMaster) - assert_attributes(attrs, names, channel) - - %w(skipHours skipDays).each do |name| - value = n_attrs[name] - if value - target = channel.__send__(name) - __send__("assert_channel09_#{name}", value, target) + suffix = "/>" + assert_parse(generator.call(content + suffix), :nothing_raised) + suffix = ">xxx</content>" + assert_parse(generator.call(content + suffix), :nothing_raised) + end + + [ + ["text", "sample content"], + ["text/plain", "sample content"], + ["html", "<em>sample</em> content"] + ].each do |type, content_content| + feed = RSS::Parser.parse(generator.call(<<-EOA)) + <content type="#{type}">#{h content_content}</content> +EOA + content = yield(feed) + assert_equal(type, content.type) + if %w(text html).include?(type) + assert(content.inline_text?) + else + assert(!content.inline_text?) + end + if type == "html" + assert(content.inline_html?) + else + assert(!content.inline_html?) + end + assert(!content.inline_xhtml?) + if type == "text/plain" + assert(content.inline_other?) + assert(content.inline_other_text?) + else + assert(!content.inline_other?) + assert(!content.inline_other_text?) end + assert(!content.inline_other_xml?) + assert(!content.inline_other_base64?) + assert(!content.out_of_line?) + assert(!content.have_xml_content?) + assert_equal(content_content, content.content) end end end - def assert_channel09_skipDays(contents, skipDays) + def assert_atom_content_inline_xhtml(generator) _wrap_assertion do - days = skipDays.days - contents.each_with_index do |content, i| - assert_equal(content, days[i].content) - end + args = ["div", "content"] + assert_parse(generator.call(<<-EOA), :missing_tag, *args) + <content type="xhtml"/> +EOA + + args = ["x", Atom::URI, "content"] + assert_parse(generator.call(<<-EOA), :not_expected_tag, *args) + <content type="xhtml"><x/></content> +EOA + + xhtml_uri = "http://www.w3.org/1999/xhtml" + xhtml_content = "<div xmlns=\"#{xhtml_uri}\">abc</div>" + xhtml_element = RSS::XML::Element.new("div", nil, xhtml_uri, + {"xmlns" => xhtml_uri}, + ["abc"]) + feed = RSS::Parser.parse(generator.call(<<-EOA)) + <content type="xhtml">#{xhtml_content}</content> +EOA + + content = yield(feed) + assert_not_nil(content) + assert_equal("xhtml", content.type) + assert(!content.inline_text?) + assert(!content.inline_html?) + assert(content.inline_xhtml?) + assert(!content.inline_other?) + assert(!content.inline_other_text?) + assert(!content.inline_other_xml?) + assert(!content.inline_other_base64?) + assert(!content.out_of_line?) + assert(content.have_xml_content?) + assert_equal(xhtml_content, content.content) + assert_equal(xhtml_element, content.xhtml) end end - - def assert_channel09_skipHours(contents, skipHours) + + def assert_atom_content_inline_other(generator, &getter) _wrap_assertion do - hours = skipHours.hours - contents.each_with_index do |content, i| - assert_equal(content.to_i, hours[i].content) - end + assert_atom_content_inline_other_text(generator, &getter) + assert_atom_content_inline_other_xml(generator, &getter) end end - - def assert_image09(attrs, image) + + def assert_atom_content_inline_other_text(generator) _wrap_assertion do - names = %w(url link title description) - names << ["width", :integer] - names << ["height", :integer] - assert_attributes(attrs, names, image) + require "zlib" + + type = "application/zip" + assert_parse(generator.call(<<-EOA), :nothing_raised) + <content type="#{type}"/> +EOA + + text = "" + char = "a" + 100.times do |i| + text << char + char.succ! + end + base64_content = Base64.encode64(Zlib::Deflate.deflate(text)) + + [false, true].each do |with_space| + xml_content = base64_content + xml_content = " #{base64_content}" if with_space + feed = RSS::Parser.parse(generator.call(<<-EOA)) + <content type="#{type}">#{xml_content}</content> +EOA + + content = yield(feed) + assert_not_nil(content) + assert_equal(type, content.type) + assert(!content.inline_text?) + assert(!content.inline_html?) + assert(!content.inline_xhtml?) + assert(content.inline_other?) + assert(!content.inline_other_text?) + assert(!content.inline_other_xml?) + assert(content.inline_other_base64?) + assert(!content.out_of_line?) + assert(!content.have_xml_content?) + assert_equal(text, Zlib::Inflate.inflate(content.content)) + + xml = REXML::Document.new(content.to_s).root + assert_rexml_element([], {"type" => type}, base64_content, xml) + end end end - def assert_items09(attrs, items) + def assert_atom_content_inline_other_xml(generator) _wrap_assertion do - names = %w(title link description) - items.each_with_index do |item, i| - assert_attributes(attrs[i], names, item) - end + type = "image/svg+xml" + + assert_parse(generator.call(<<-EOA), :nothing_raised) + <content type="#{type}"/> +EOA + + svg_uri = "http://www.w3.org/2000/svg" + svg_width = "50pt" + svg_height = "20pt" + svg_version = "1.0" + text_x = "15" + text_y = "15" + text = "text" + svg_content = <<-EOS +<svg + xmlns="#{svg_uri}" + width="#{svg_width}" + height="#{svg_height}" + version="#{svg_version}" +><text x="#{text_x}" y="#{text_y}">#{text}</text +></svg> +EOS + + text_element = RSS::XML::Element.new("text", nil, svg_uri, + { + "x" => text_x, + "y" => text_y, + }, + [text]) + svg_element = RSS::XML::Element.new("svg", nil, svg_uri, + { + "xmlns" => svg_uri, + "width" => svg_width, + "height" => svg_height, + "version" => svg_version, + }, + [text_element]) + feed = RSS::Parser.parse(generator.call(<<-EOA)) + <content type="#{type}">#{svg_content}</content> +EOA + + content = yield(feed) + assert_not_nil(content) + assert_equal(type, content.type) + assert(!content.inline_text?) + assert(!content.inline_html?) + assert(!content.inline_xhtml?) + assert(content.inline_other?) + assert(!content.inline_other_text?) + assert(content.inline_other_xml?) + assert(!content.inline_other_base64?) + assert(!content.out_of_line?) + assert(content.have_xml_content?) + assert_equal(REXML::Document.new(svg_content).to_s.chomp, + REXML::Document.new(content.content).to_s.chomp) + assert_equal(svg_element, content.xml) + assert_nil(content.xhtml) end end - - def assert_textinput09(attrs, textinput) + + def assert_atom_content_out_of_line(generator) _wrap_assertion do - names = %w(title description name link) - assert_attributes(attrs, names, textinput) + text_type = "text/plain" + text_src = "http://example.com/README.txt" + + missing_args = [:missing_attribute, "content", "type"] + # RSS Parser raises error even if this is "should" not "must". + assert_parse(generator.call(<<-EOA), *missing_args) + <content src="#{text_src}"/> +EOA + + content_content = "xxx" + not_available_value_args = [:not_available_value, + "content", content_content] + assert_parse(generator.call(<<-EOA), *not_available_value_args) + <content type="#{text_type}" src="#{text_src}">#{content_content}</content> +EOA + + feed = RSS::Parser.parse(generator.call(<<-EOA)) + <content type="#{text_type}" src="#{text_src}"/> +EOA + content = yield(feed) + assert_not_nil(content) + assert_equal(text_type, content.type) + assert_equal(text_src, content.src) + assert(!content.inline_text?) + assert(!content.inline_html?) + assert(!content.inline_xhtml?) + assert(!content.inline_other?) + assert(!content.inline_other_text?) + assert(!content.inline_other_xml?) + assert(!content.inline_other_base64?) + assert(content.out_of_line?) + assert(!content.have_xml_content?) + assert_nil(content.xml) + assert_nil(content.xhtml) + assert_equal("", content.content) end end - - def assert_channel20(attrs, channel) + def assert_atom_source(generator, &getter) _wrap_assertion do - n_attrs = normalized_attrs(attrs) - - names = %w(title link description language copyright - managingEditor webMaster pubDate - lastBuildDate generator docs rating) - names << ["ttl", :integer] - assert_attributes(attrs, names, channel) - - %w(cloud categories skipHours skipDays).each do |name| - value = n_attrs[name] - if value - target = channel.__send__(name) - __send__("assert_channel20_#{name}", value, target) - end - end + assert_atom_source_author(generator, &getter) + assert_atom_source_category(generator, &getter) + assert_atom_source_contributor(generator, &getter) + assert_atom_source_generator(generator, &getter) + assert_atom_source_icon(generator, &getter) + assert_atom_source_id(generator, &getter) + assert_atom_source_link(generator, &getter) + assert_atom_source_logo(generator, &getter) + assert_atom_source_rights(generator, &getter) + assert_atom_source_subtitle(generator, &getter) + assert_atom_source_title(generator, &getter) + assert_atom_source_updated(generator, &getter) end end - def assert_channel20_skipDays(contents, skipDays) - assert_channel09_skipDays(contents, skipDays) - end - - def assert_channel20_skipHours(contents, skipHours) - assert_channel09_skipHours(contents, skipHours) + def assert_atom_source_author(generator) + assert_atom_person("author", generator) do |feed| + source = yield(feed) + assert_equal(1, source.authors.size) + source.author + end end - - def assert_channel20_cloud(attrs, cloud) - _wrap_assertion do - names = %w(domain path registerProcedure protocol) - names << ["port", :integer] - assert_attributes(attrs, names, cloud) + + def assert_atom_source_category(generator) + assert_atom_category(generator) do |feed| + source = yield(feed) + assert_equal(1, source.categories.size) + source.category end end - - def assert_channel20_categories(attrs, categories) - _wrap_assertion do - names = %w(domain content) - categories.each_with_index do |category, i| - assert_attributes(attrs[i], names, category) - end + + def assert_atom_source_contributor(generator) + assert_atom_person("contributor", generator) do |feed| + source = yield(feed) + assert_equal(1, source.contributors.size) + source.contributor end end - - def assert_image20(attrs, image) - _wrap_assertion do - names = %w(url link title description) - names << ["width", :integer] - names << ["height", :integer] - assert_attributes(attrs, names, image) + + def assert_atom_source_generator(generator) + assert_atom_generator(generator) do |feed| + yield(feed).generator end end - def assert_items20(attrs, items) - _wrap_assertion do - names = %w(about title link description) - items.each_with_index do |item, i| - assert_attributes(attrs[i], names, item) + def assert_atom_source_icon(generator) + assert_atom_icon(generator) do |feed| + yield(feed).icon + end + end - n_attrs = normalized_attrs(attrs[i]) + def assert_atom_source_id(generator) + id_content = "urn:uuid:a2fb588b-5674-4898-b420-265a734fea69" + id = "<id>#{id_content}</id>" + feed = RSS::Parser.parse(generator.call(id)) + assert_equal(id_content, yield(feed).id.content) + end - %w(source enclosure categories guid).each do |name| - value = n_attrs[name] - if value - target = item.__send__(name) - __send__("assert_items20_#{name}", value, target) - end - end - end + def assert_atom_source_link(generator) + assert_atom_link(generator) do |feed| + source = yield(feed) + assert_equal(1, source.links.size) + source.link end end - def assert_items20_source(attrs, source) - _wrap_assertion do - assert_attributes(attrs, %w(url content), source) + def assert_atom_source_logo(generator) + assert_atom_logo(generator) do |feed| + yield(feed).logo end end - - def assert_items20_enclosure(attrs, enclosure) - _wrap_assertion do - names = ["url", ["length", :integer], "type"] - assert_attributes(attrs, names, enclosure) + + def assert_atom_source_rights(generator) + assert_atom_text_construct("rights", generator) do |feed| + yield(feed).rights end end - - def assert_items20_categories(attrs, categories) - _wrap_assertion do - assert_channel20_categories(attrs, categories) + + def assert_atom_source_subtitle(generator) + assert_atom_text_construct("subtitle", generator) do |feed| + yield(feed).subtitle end end - - def assert_items20_guid(attrs, guid) - _wrap_assertion do - names = [["isPermaLink", :boolean], ["content"]] - assert_attributes(attrs, names, guid) + + def assert_atom_source_title(generator) + assert_atom_text_construct("title", generator) do |feed| + yield(feed).title end end - def assert_textinput20(attrs, textinput) - _wrap_assertion do - names = %w(title description name link) - assert_attributes(attrs, names, textinput) + def assert_atom_source_updated(generator) + assert_atom_date_construct("updated", generator) do |feed| + yield(feed).updated end end - def assert_dublin_core(elems, target) _wrap_assertion do elems.each do |name, value| @@ -493,7 +896,1147 @@ module RSS end end end - + + def assert_rexml_element(children, attrs, text, element, text_type=nil) + _wrap_assertion do + if children + children_info = element.elements.collect {|e| [e.namespace, e.name]} + assert_equal(children.collect {|uri, name| [uri, name]}.sort, + children_info.sort) + end + if attrs + assert_equal(attrs.collect {|k, v| [k, v]}.sort, + element.attributes.collect {|k, v| [k, v]}.sort) + end + case text_type + when :time + assert_not_nil(element.text) + assert_equal(Time.parse(text).to_s, Time.parse(element.text).to_s) + else + assert_equal(text, element.text) + end + end + end + + def _assert_maker_atom_persons(feed_type, maker_readers, feed_readers) + _wrap_assertion do + persons = [] + feed = RSS::Maker.make("atom:#{feed_type}") do |maker| + yield maker + targets = chain_reader(maker, maker_readers) + targets.each do |target| + person = { + :name => target.name, + :uri => target.uri, + :email => target.email, + } + persons << person if person[:name] + end + end + + actual_persons = chain_reader(feed, feed_readers) || [] + actual_persons = actual_persons.collect do |person| + { + :name => person.name ? person.name.content : nil, + :uri => person.uri ? person.uri.content : nil, + :email => person.email ? person.email.content : nil, + } + end + assert_equal(persons, actual_persons) + end + end + + def assert_maker_atom_persons(feed_type, maker_readers, feed_readers, + not_set_error_name=nil, + parent_not_set_error_name=nil, + parent_not_set_variable=nil) + _wrap_assertion do + not_set_error_name ||= "maker.#{maker_readers.join('.')}" + + args = [feed_type, maker_readers, feed_readers] + if parent_not_set_error_name or parent_not_set_variable + assert_not_set_error(parent_not_set_error_name, + parent_not_set_variable) do + _assert_maker_atom_persons(*args) do |maker| + yield maker + end + end + else + _assert_maker_atom_persons(*args) do |maker| + yield maker + end + end + + assert_not_set_error(not_set_error_name, %w(name)) do + _assert_maker_atom_persons(feed_type, maker_readers, + feed_readers) do |maker| + yield maker + targets = chain_reader(maker, maker_readers) + target = targets.new_child + end + end + + assert_not_set_error(not_set_error_name, %w(name)) do + _assert_maker_atom_persons(feed_type, maker_readers, + feed_readers) do |maker| + yield maker + targets = chain_reader(maker, maker_readers) + target = targets.new_child + target.uri = "http://example.com/~me/" + end + end + + assert_not_set_error(not_set_error_name, %w(name)) do + _assert_maker_atom_persons(feed_type, maker_readers, + feed_readers) do |maker| + yield maker + targets = chain_reader(maker, maker_readers) + target = targets.new_child + target.email = "me@example.com" + end + end + + assert_not_set_error(not_set_error_name, %w(name)) do + _assert_maker_atom_persons(feed_type, maker_readers, + feed_readers) do |maker| + yield maker + targets = chain_reader(maker, maker_readers) + target = targets.new_child + target.uri = "http://example.com/~me/" + target.email = "me@example.com" + end + end + + _assert_maker_atom_persons(feed_type, maker_readers, + feed_readers) do |maker| + yield maker + targets = chain_reader(maker, maker_readers) + target = targets.new_child + target.name = "me" + end + + _assert_maker_atom_persons(feed_type, maker_readers, + feed_readers) do |maker| + yield maker + targets = chain_reader(maker, maker_readers) + target = targets.new_child + target.name = "me" + target.uri = "http://example.com/~me/" + end + + _assert_maker_atom_persons(feed_type, maker_readers, + feed_readers) do |maker| + yield maker + targets = chain_reader(maker, maker_readers) + target = targets.new_child + target.name = "me" + target.email = "me@example.com" + end + + _assert_maker_atom_persons(feed_type, maker_readers, + feed_readers) do |maker| + yield maker + targets = chain_reader(maker, maker_readers) + target = targets.new_child + target.name = "me" + target.uri = "http://example.com/~me/" + target.email = "me@example.com" + end + + _assert_maker_atom_persons(feed_type, maker_readers, + feed_readers) do |maker| + yield maker + targets = chain_reader(maker, maker_readers) + + target = targets.new_child + target.name = "me" + target.uri = "http://example.com/~me/" + target.email = "me@example.com" + + target = targets.new_child + target.name = "you" + target.uri = "http://example.com/~you/" + target.email = "you@example.com" + end + + assert_not_set_error(not_set_error_name, %w(name)) do + _assert_maker_atom_persons(feed_type, maker_readers, + feed_readers) do |maker| + yield maker + targets = chain_reader(maker, maker_readers) + + target = targets.new_child + target.name = "me" + target.uri = "http://example.com/~me/" + target.email = "me@example.com" + + target = targets.new_child + end + end + end + end + + def _assert_maker_atom_text_construct(feed_type, maker_readers, + feed_readers, &block) + maker_extractor = Proc.new do |target| + text = { + :type => target.type, + :content => target.content, + :xml_content => target.xml_content, + } + if text[:type] == "xhtml" + if text[:xml_content] + xml_content = text[:xml_content] + xhtml_uri = "http://www.w3.org/1999/xhtml" + unless xml_content.is_a?(RSS::XML::Element) and + ["div", xhtml_uri] == [xml_content.name, xml_content.uri] + children = xml_content + children = [children] unless children.is_a?(Array) + xml_content = RSS::XML::Element.new("div", nil, xhtml_uri, + {"xmlns" => xhtml_uri}, + children) + text[:xml_content] = xml_content + end + text + else + nil + end + else + text[:content] ? text : nil + end + end + feed_extractor = Proc.new do |target| + { + :type => target.type, + :content => target.content, + :xml_content => target.xhtml, + } + end + _assert_maker_atom_element(feed_type, maker_readers, feed_readers, + maker_extractor, feed_extractor, + &block) + end + + def assert_maker_atom_text_construct(feed_type, maker_readers, feed_readers, + parent_not_set_error_name=nil, + parent_not_set_variable=nil, + not_set_error_name=nil) + _wrap_assertion do + not_set_error_name ||= "maker.#{maker_readers.join('.')}" + + args = [feed_type, maker_readers, feed_readers] + if parent_not_set_error_name or parent_not_set_variable + assert_not_set_error(parent_not_set_error_name, + parent_not_set_variable) do + _assert_maker_atom_text_construct(*args) do |maker| + yield maker + end + end + else + _assert_maker_atom_text_construct(*args) do |maker| + yield maker + end + end + + assert_not_set_error(not_set_error_name, %w(content)) do + _assert_maker_atom_text_construct(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) {|x| x} + target.type = "text" + end + end + + assert_not_set_error(not_set_error_name, %w(content)) do + _assert_maker_atom_text_construct(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) {|x| x} + target.type = "html" + end + end + + assert_not_set_error(not_set_error_name, %w(xml_content)) do + _assert_maker_atom_text_construct(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) {|x| x} + target.type = "xhtml" + end + end + + assert_not_set_error(not_set_error_name, %w(xml_content)) do + _assert_maker_atom_text_construct(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) {|x| x} + target.type = "xhtml" + target.content = "Content" + end + end + + _assert_maker_atom_text_construct(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) {|x| x} + target.type = "text" + target.content = "Content" + end + + _assert_maker_atom_text_construct(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) {|x| x} + target.type = "html" + target.content = "<em>Content</em>" + end + + _assert_maker_atom_text_construct(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) {|x| x} + target.type = "xhtml" + target.xml_content = "text only" + end + + _assert_maker_atom_text_construct(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) {|x| x} + target.type = "xhtml" + target.xml_content = RSS::XML::Element.new("unknown") + end + end + end + + def _assert_maker_atom_date_construct(feed_type, maker_readers, + feed_readers, &block) + maker_extractor = Proc.new do |target| + date = { + :content => target, + } + date[:content] ? date : nil + end + feed_extractor = Proc.new do |target| + { + :content => target.content, + } + end + _assert_maker_atom_element(feed_type, maker_readers, feed_readers, + maker_extractor, feed_extractor, + &block) + end + + def assert_maker_atom_date_construct(feed_type, maker_readers, feed_readers, + parent_not_set_error_name=nil, + parent_not_set_variable=nil) + _wrap_assertion do + args = [feed_type, maker_readers, feed_readers] + if parent_not_set_error_name or parent_not_set_variable + assert_not_set_error(parent_not_set_error_name, + parent_not_set_variable) do + _assert_maker_atom_date_construct(*args) do |maker| + yield maker + end + end + else + _assert_maker_atom_date_construct(*args) do |maker| + yield maker + end + end + + maker_readers = maker_readers.dup + writer = "#{maker_readers.pop}=" + _assert_maker_atom_date_construct(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.__send__(writer, Time.now) + end + end + end + + def _assert_maker_atom_element(feed_type, maker_readers, feed_readers, + maker_extractor, feed_extractor) + _wrap_assertion do + element = nil + feed = RSS::Maker.make("atom:#{feed_type}") do |maker| + yield maker + target = chain_reader(maker, maker_readers) {|x| x} + element = maker_extractor.call(target) + end + + target = chain_reader(feed, feed_readers) + if target + actual_element = feed_extractor.call(target) + else + actual_element = nil + end + assert_equal(element, actual_element) + end + end + + def _assert_maker_atom_elements(feed_type, maker_readers, feed_readers, + maker_extractor, feed_extractor, + invalid_feed_checker=nil) + _wrap_assertion do + elements = [] + invalid_feed = false + feed = RSS::Maker.make("atom:#{feed_type}") do |maker| + yield maker + targets = chain_reader(maker, maker_readers) + targets.each do |target| + element = maker_extractor.call(target) + elements << element if element + end + if invalid_feed_checker + invalid_feed = invalid_feed_checker.call(targets) + end + end + + if invalid_feed + assert_nil(feed) + else + actual_elements = chain_reader(feed, feed_readers) || [] + actual_elements = actual_elements.collect do |target| + feed_extractor.call(target) + end + assert_equal(elements, actual_elements) + end + end + end + + def assert_maker_atom_element(feed_type, maker_readers, feed_readers, + setup_target, optional_variables, + required_variable, assert_method_name, + not_set_error_name=nil, + *additional_args) + _wrap_assertion do + not_set_error_name ||= "maker.#{maker_readers.join('.')}" + + 0.upto(optional_variables.size) do |i| + combination(optional_variables, i).each do |names| + have = {} + names.each do |name| + have[name.intern] = true + end + have_required_variable_too = + have.merge({required_variable.intern => true}) + + assert_not_set_error(not_set_error_name, [required_variable]) do + __send__(assert_method_name, feed_type, maker_readers, + feed_readers, *additional_args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) {|x| x} + setup_target.call(target, have) + end + end + + __send__(assert_method_name, feed_type, maker_readers, feed_readers, + *additional_args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) {|x| x} + setup_target.call(target, have_required_variable_too) + end + end + end + end + end + + def assert_maker_atom_elements(feed_type, maker_readers, feed_readers, + setup_target, optional_variables, + required_variable, assert_method_name, + not_set_error_name=nil, + *additional_args) + _wrap_assertion do + not_set_error_name ||= "maker.#{maker_readers.join('.')}" + + 0.upto(optional_variables.size) do |i| + combination(optional_variables, i).each do |names| + have = {} + names.each do |name| + have[name.intern] = true + end + have_required_variable_too = + have.merge({required_variable.intern => true}) + + assert_not_set_error(not_set_error_name, [required_variable]) do + __send__(assert_method_name, feed_type, maker_readers, + feed_readers, *additional_args) do |maker| + yield maker + targets = chain_reader(maker, maker_readers) + setup_target.call(targets, have) + end + end + + __send__(assert_method_name, feed_type, maker_readers, feed_readers, + *additional_args) do |maker| + yield maker + targets = chain_reader(maker, maker_readers) + setup_target.call(targets, have_required_variable_too) + end + + __send__(assert_method_name, feed_type, maker_readers, feed_readers, + *additional_args) do |maker| + yield maker + targets = chain_reader(maker, maker_readers) + setup_target.call(targets, have_required_variable_too) + setup_target.call(targets, have_required_variable_too) + end + + assert_not_set_error(not_set_error_name, [required_variable]) do + __send__(assert_method_name, feed_type, maker_readers, feed_readers, + *additional_args) do |maker| + yield maker + targets = chain_reader(maker, maker_readers) + setup_target.call(targets, have_required_variable_too) + setup_target.call(targets, have) + end + end + end + end + end + end + + def _assert_maker_atom_categories(feed_type, maker_readers, + feed_readers, &block) + maker_extractor = Proc.new do |target| + category = { + :term => target.term, + :scheme => target.scheme, + :label => target.label, + } + category[:term] ? category : nil + end + feed_extractor = Proc.new do |target| + { + :term => target.term, + :scheme => target.scheme, + :label => target.label, + } + end + _assert_maker_atom_elements(feed_type, maker_readers, feed_readers, + maker_extractor, feed_extractor, &block) + end + + def assert_maker_atom_categories(feed_type, maker_readers, feed_readers, + not_set_error_name=nil, &block) + _wrap_assertion do + _assert_maker_atom_categories(feed_type, maker_readers, + feed_readers) do |maker| + yield maker + end + + setup_target = Proc.new do |targets, have| + target = targets.new_child + target.term = "music" if have[:term] + target.scheme = "http://example.com/category/music" if have[:scheme] + target.label = "Music" if have[:label] + end + + optional_variables = %w(scheme label) + + assert_maker_atom_elements(feed_type, maker_readers, feed_readers, + setup_target, optional_variables, + "term", :_assert_maker_atom_categories, + not_set_error_name, &block) + end + end + + def _assert_maker_atom_generator(feed_type, maker_readers, + feed_readers, &block) + maker_extractor = Proc.new do |target| + generator = { + :uri => target.uri, + :version => target.version, + :content => target.content, + } + generator[:content] ? generator : nil + end + feed_extractor = Proc.new do |target| + { + :uri => target.uri, + :version => target.version, + :content => target.content, + } + end + _assert_maker_atom_element(feed_type, maker_readers, feed_readers, + maker_extractor, feed_extractor, + &block) + end + + def assert_maker_atom_generator(feed_type, maker_readers, feed_readers, + not_set_error_name=nil, &block) + _wrap_assertion do + not_set_error_name ||= "maker.#{maker_readers.join('.')}" + + _assert_maker_atom_generator(feed_type, maker_readers, + feed_readers) do |maker| + yield maker + end + + setup_target = Proc.new do |target, have| + target.content = "RSS Maker" if have[:content] + target.uri = "http://example.com/rss/maker" if have[:uri] + target.version = "0.0.1" if have[:version] + end + + optional_variables = %w(uri version) + + assert_maker_atom_element(feed_type, maker_readers, feed_readers, + setup_target, optional_variables, + "content", :_assert_maker_atom_generator, + not_set_error_name, &block) + end + end + + def _assert_maker_atom_icon(feed_type, maker_readers, feed_readers, + accessor_base, &block) + maker_extractor = Proc.new do |target| + icon = { + :content => target.__send__(accessor_base), + } + icon[:content] ? icon : nil + end + feed_extractor = Proc.new do |target| + { + :content => target.content, + } + end + _assert_maker_atom_element(feed_type, maker_readers, feed_readers, + maker_extractor, feed_extractor, + &block) + end + + def assert_maker_atom_icon(feed_type, maker_readers, feed_readers, + accessor_base=nil, not_set_error_name=nil) + _wrap_assertion do + accessor_base ||= "url" + not_set_error_name ||= "maker.#{maker_readers.join('.')}" + + _assert_maker_atom_icon(feed_type, maker_readers, feed_readers, + accessor_base) do |maker| + yield maker + end + + _assert_maker_atom_icon(feed_type, maker_readers, feed_readers, + accessor_base) do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.__send__("#{accessor_base}=", "http://example.com/icon.png") + end + end + end + + def _assert_maker_atom_links(feed_type, maker_readers, feed_readers, + allow_duplication=false, &block) + maker_extractor = Proc.new do |target| + link = { + :href => target.href, + :rel => target.rel, + :type => target.type, + :hreflang => target.hreflang, + :title => target.title, + :length => target.length, + } + link[:href] ? link : nil + end + feed_extractor = Proc.new do |target| + { + :href => target.href, + :rel => target.rel, + :type => target.type, + :hreflang => target.hreflang, + :title => target.title, + :length => target.length, + } + end + invalid_feed_checker = Proc.new do |targets| + infos = {} + invalid = false + targets.each do |target| + key = [target.hreflang, target.type] + if infos.has_key?(key) + invalid = true + break + end + infos[key] = true if target.rel.nil? or target.rel == "alternate" + end + invalid + end + invalid_feed_checker = nil if allow_duplication + _assert_maker_atom_elements(feed_type, maker_readers, feed_readers, + maker_extractor, feed_extractor, + invalid_feed_checker, + &block) + end + + def assert_maker_atom_links(feed_type, maker_readers, feed_readers, + not_set_error_name=nil, allow_duplication=false, + &block) + _wrap_assertion do + _assert_maker_atom_links(feed_type, maker_readers, + feed_readers) do |maker| + yield maker + end + + langs = %(ja en fr zh po) + setup_target = Proc.new do |targets, have| + target = targets.new_child + lang = langs[targets.size % langs.size] + target.href = "http://example.com/index.html.#{lang}" if have[:href] + target.rel = "alternate" if have[:rel] + target.type = "text/xhtml" if have[:type] + target.hreflang = lang if have[:hreflang] + target.title = "FrontPage(#{lang})" if have[:title] + target.length = 1024 if have[:length] + end + + optional_variables = %w(rel type hreflang title length) + + assert_maker_atom_elements(feed_type, maker_readers, feed_readers, + setup_target, optional_variables, + "href", :_assert_maker_atom_links, + not_set_error_name, allow_duplication, + &block) + end + end + + def _assert_maker_atom_logo(feed_type, maker_readers, feed_readers, + accessor_base, &block) + maker_extractor = Proc.new do |target| + logo = { + :uri => target.__send__(accessor_base), + } + logo[:uri] ? logo : nil + end + feed_extractor = Proc.new do |target| + { + :uri => target.content, + } + end + _assert_maker_atom_element(feed_type, maker_readers, feed_readers, + maker_extractor, feed_extractor, + &block) + end + + def assert_maker_atom_logo(feed_type, maker_readers, feed_readers, + accessor_base=nil, not_set_error_name=nil) + _wrap_assertion do + accessor_base ||= "uri" + not_set_error_name ||= "maker.#{maker_readers.join('.')}" + + _assert_maker_atom_logo(feed_type, maker_readers, feed_readers, + accessor_base) do |maker| + yield maker + end + + _assert_maker_atom_logo(feed_type, maker_readers, feed_readers, + accessor_base) do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.__send__("#{accessor_base}=", "http://example.com/logo.png") + end + end + end + + def _assert_maker_atom_id(feed_type, maker_readers, feed_readers, &block) + maker_extractor = Proc.new do |target| + id = { + :uri => target.id, + } + id[:uri] ? id : nil + end + feed_extractor = Proc.new do |target| + if target.id + { + :uri => target.id.content, + } + else + nil + end + end + _assert_maker_atom_element(feed_type, maker_readers, feed_readers, + maker_extractor, feed_extractor, + &block) + end + + def assert_maker_atom_id(feed_type, maker_readers, feed_readers, + not_set_error_name=nil) + _wrap_assertion do + not_set_error_name ||= "maker.#{maker_readers.join('.')}" + + args = [feed_type, maker_readers, feed_readers] + _assert_maker_atom_id(*args) do |maker| + yield maker + end + + _assert_maker_atom_id(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.id = "http://example.com/id/1" + end + end + end + + def _assert_maker_atom_content(feed_type, maker_readers, + feed_readers, &block) + maker_extractor = Proc.new do |target| + content = { + :type => target.type, + :src => target.src, + :content => target.content, + :xml => target.xml, + :inline_text => target.inline_text?, + :inline_html => target.inline_html?, + :inline_xhtml => target.inline_xhtml?, + :inline_other => target.inline_other?, + :inline_other_text => target.inline_other_text?, + :inline_other_xml => target.inline_other_xml?, + :inline_other_base64 => target.inline_other_base64?, + :out_of_line => target.out_of_line?, + } + content[:src] = nil if content[:src] and content[:content] + if content[:type] or content[:content] + content + else + nil + end + end + feed_extractor = Proc.new do |target| + { + :type => target.type, + :src => target.src, + :content => target.content, + :xml => target.xml, + :inline_text => target.inline_text?, + :inline_html => target.inline_html?, + :inline_xhtml => target.inline_xhtml?, + :inline_other => target.inline_other?, + :inline_other_text => target.inline_other_text?, + :inline_other_xml => target.inline_other_xml?, + :inline_other_base64 => target.inline_other_base64?, + :out_of_line => target.out_of_line?, + } + end + _assert_maker_atom_element(feed_type, maker_readers, feed_readers, + maker_extractor, feed_extractor, + &block) + end + + def assert_maker_atom_content(feed_type, maker_readers, feed_readers, + not_set_error_name=nil, &block) + _wrap_assertion do + not_set_error_name ||= "maker.#{maker_readers.join('.')}" + args = [feed_type, maker_readers, feed_readers, not_set_error_name] + assert_maker_atom_content_inline_text(*args, &block) + assert_maker_atom_content_inline_xhtml(*args, &block) + assert_maker_atom_content_inline_other(*args, &block) + assert_maker_atom_content_out_of_line(*args, &block) + end + end + + def assert_maker_atom_content_inline_text(feed_type, maker_readers, + feed_readers, not_set_error_name) + _wrap_assertion do + args = [feed_type, maker_readers, feed_readers] + _assert_maker_atom_content(*args) do |maker| + yield maker + end + + assert_not_set_error(not_set_error_name, %w(content)) do + RSS::Maker.make("atom:#{feed_type}") do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "text" + end + end + + assert_not_set_error(not_set_error_name, %w(content)) do + RSS::Maker.make("atom:#{feed_type}") do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "html" + end + end + + _assert_maker_atom_content(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.content = "" + end + + _assert_maker_atom_content(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "text" + target.content = "example content" + end + + _assert_maker_atom_content(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "html" + target.content = "<em>text</em>" + end + end + end + + def assert_maker_atom_content_inline_xhtml(feed_type, maker_readers, + feed_readers, not_set_error_name) + _wrap_assertion do + args = [feed_type, maker_readers, feed_readers] + assert_not_set_error(not_set_error_name, %w(xml_content)) do + RSS::Maker.make("atom:#{feed_type}") do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "xhtml" + end + end + + assert_not_set_error(not_set_error_name, %w(xml_content)) do + RSS::Maker.make("atom:#{feed_type}") do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "xhtml" + target.content = "dummy" + end + end + + _assert_maker_atom_content(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "xhtml" + target.xml_content = "text" + end + + _assert_maker_atom_content(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "xhtml" + target.xml = "text" + end + + _assert_maker_atom_content(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "xhtml" + target.xml_content = + RSS::XML::Element.new("em", nil, nil, {}, ["text"]) + end + + _assert_maker_atom_content(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "xhtml" + target.xml = RSS::XML::Element.new("em", nil, nil, {}, ["text"]) + end + + + xhtml_uri = "http://www.w3.org/1999/xhtml" + em = RSS::XML::Element.new("em", nil, nil, {}, ["text"]) + em_with_xhtml_uri = + RSS::XML::Element.new("em", nil, xhtml_uri, {}, ["text"]) + feed = RSS::Maker.make("atom:#{feed_type}") do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "xhtml" + target.xml = em + end + assert_equal(RSS::XML::Element.new("div", nil, xhtml_uri, + {"xmlns" => xhtml_uri}, + [em_with_xhtml_uri]), + chain_reader(feed, feed_readers).xml) + + div = RSS::XML::Element.new("div", nil, xhtml_uri, + {"xmlns" => xhtml_uri, + "class" => "sample"}, + ["text"]) + feed = RSS::Maker.make("atom:#{feed_type}") do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "xhtml" + target.xml = div + end + assert_equal(div, chain_reader(feed, feed_readers).xml) + end + end + + def assert_maker_atom_content_inline_other(*args, &block) + _wrap_assertion do + assert_maker_atom_content_inline_other_xml(*args, &block) + assert_maker_atom_content_inline_other_text(*args, &block) + assert_maker_atom_content_inline_other_base64(*args, &block) + end + end + + def assert_maker_atom_content_inline_other_xml(feed_type, maker_readers, + feed_readers, + not_set_error_name) + _wrap_assertion do + args = [feed_type, maker_readers, feed_readers] + assert_not_set_error(not_set_error_name, %w(xml_content)) do + RSS::Maker.make("atom:#{feed_type}") do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "application/xml" + end + end + + assert_not_set_error(not_set_error_name, %w(xml_content)) do + RSS::Maker.make("atom:#{feed_type}") do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "svg/image+xml" + end + end + + svg_uri = "http://www.w3.org/2000/svg" + rect = RSS::XML::Element.new("rect", nil, svg_uri, + {"x" => "0.5cm", + "y" => "0.5cm", + "width" => "2cm", + "height" => "1cm"}) + svg = RSS::XML::Element.new("svg", nil, svg_uri, + {"xmlns" => svg_uri, + "version" => "1.1", + "width" => "5cm", + "height" => "4cm"}, + [rect]) + _assert_maker_atom_content(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "image/svg+xml" + target.xml = svg + end + + feed = RSS::Maker.make("atom:#{feed_type}") do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "image/svg+xml" + target.xml = svg + end + assert_equal(svg, chain_reader(feed, feed_readers).xml) + end + end + + def assert_maker_atom_content_inline_other_text(feed_type, maker_readers, + feed_readers, + not_set_error_name) + _wrap_assertion do + args = [feed_type, maker_readers, feed_readers] + assert_not_set_error(not_set_error_name, %w(content)) do + RSS::Maker.make("atom:#{feed_type}") do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "text/plain" + end + end + + _assert_maker_atom_content(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "text/plain" + target.content = "text" + end + end + end + + def assert_maker_atom_content_inline_other_base64(feed_type, maker_readers, + feed_readers, + not_set_error_name) + _wrap_assertion do + args = [feed_type, maker_readers, feed_readers] + content = "\211PNG\r\n\032\n" + _assert_maker_atom_content(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "image/png" + target.content = content + end + + _assert_maker_atom_content(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "image/png" + target.src = "http://example.com/logo.png" + target.content = content + end + + feed = RSS::Maker.make("atom:#{feed_type}") do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "image/png" + target.src = "http://example.com/logo.png" + target.content = content + end + target = chain_reader(feed, feed_readers) + assert_nil(target.src) + assert_equal(content, target.content) + end + end + + def assert_maker_atom_content_out_of_line(feed_type, maker_readers, + feed_readers, not_set_error_name) + _wrap_assertion do + args = [feed_type, maker_readers, feed_readers] + assert_not_set_error(not_set_error_name, %w(content)) do + RSS::Maker.make("atom:#{feed_type}") do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "image/png" + end + end + + assert_not_set_error(not_set_error_name, %w(type)) do + RSS::Maker.make("atom:#{feed_type}") do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.src = "http://example.com/logo.png" + end + end + + _assert_maker_atom_content(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "image/png" + target.src = "http://example.com/logo.png" + end + + _assert_maker_atom_content(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "image/png" + target.content = "\211PNG\r\n\032\n" + end + + _assert_maker_atom_content(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "application/xml" + target.src = "http://example.com/sample.xml" + end + + + _assert_maker_atom_content(*args) do |maker| + yield maker + target = chain_reader(maker, maker_readers) + target.type = "text/plain" + target.src = "http://example.com/README.txt" + end + end + end + + def assert_slash_elements(expected, target) + assert_equal(expected, + { + "section" => target.slash_section, + "department" => target.slash_department, + "comments" => target.slash_comments, + "hit_parades" => target.slash_hit_parades, + }) + assert_equal(expected["hit_parades"].join(","), + target.slash_hit_parade) + end + + def chain_reader(target, readers, &block) + readers.inject(target) do |result, reader| + return nil if result.nil? + result.__send__(reader, &block) + end + end + def normalized_attrs(attrs) n_attrs = {} attrs.each do |name, value| @@ -501,6 +2044,31 @@ module RSS end n_attrs end - + + def combination(elements, n) + if n <= 0 or elements.size < n + [] + elsif n == 1 + elements.collect {|element| [element]} + else + first, *rest = elements + combination(rest, n - 1).collect do |sub_elements| + [first, *sub_elements] + end + combination(rest, n) + end + end + + def tag(name, content=nil, attributes={}) + attributes = attributes.collect do |key, value| + "#{ERB::Util.h(key)}=\"#{ERB::Util.h(value)}\"" + end.join(" ") + begin_tag = "<#{name}" + begin_tag << " #{attributes}" unless attributes.empty? + if content + "#{begin_tag}>#{content}</#{name}>\n" + else + "#{begin_tag}/>\n" + end + end end end diff --git a/test/rss/rss-testcase.rb b/test/rss/rss-testcase.rb index 21670bc05c..0fd9b5263f 100644 --- a/test/rss/rss-testcase.rb +++ b/test/rss/rss-testcase.rb @@ -3,6 +3,8 @@ require "erb" require "test/unit" require 'rss-assertions' +require "rss" + module RSS class TestCase < Test::Unit::TestCase include ERB::Util @@ -22,7 +24,7 @@ module RSS NAME_VALUE = "hogehoge" LANGUAGE_VALUE = "ja" DESCRIPTION_VALUE = " - XML.com features a rich mix of information and services + XML.com features a rich mix of information and services for the XML community. " RESOURCES = [ @@ -42,6 +44,45 @@ module RSS CATEGORY_DOMAIN = "http://www.superopendirectory.com/" + FEED_TITLE = "dive into mark" + FEED_UPDATED = "2003-12-13T18:30:02Z" + FEED_AUTHOR_NAME = "John Doe" + FEED_ID = "urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6" + + ENTRY_TITLE = "Atom-Powered Robots Run Amok" + ENTRY_LINK = "http://example.org/2003/12/13/atom03" + ENTRY_ID = "urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a" + ENTRY_UPDATED = "2003-12-13T18:30:02Z" + ENTRY_SUMMARY = "Some text." + + t = Time.iso8601("2000-01-01T12:00:05+00:00") + class << t + alias_method(:to_s, :iso8601) + end + + DC_ELEMENTS = { + :title => "hoge", + :description => + " XML is placing increasingly heavy loads on + the existing technical infrastructure of the Internet.", + :creator => "Rael Dornfest (mailto:rael@oreilly.com)", + :subject => "XML", + :publisher => "The O'Reilly Network", + :contributor => "hogehoge", + :type => "fugafuga", + :format => "hohoho", + :identifier => "fufufu", + :source => "barbar", + :language => "ja", + :relation => "cococo", + :rights => "Copyright (c) 2000 O'Reilly & Associates, Inc.", + :date => t, + } + + DC_NODES = DC_ELEMENTS.collect do |name, value| + "<#{DC_PREFIX}:#{name}>#{value}</#{DC_PREFIX}:#{name}>" + end.join("\n") + def default_test # This class isn't tested end @@ -205,6 +246,82 @@ EOC EOR end + def make_feed_without_entry(content=nil, xmlns=[]) + <<-EOA +<feed xmlns="#{Atom::URI}" +#{xmlns.collect {|pre, uri| "xmlns:#{pre}='#{uri}'"}.join(' ')}> + <id>#{FEED_ID}</id> + <title>#{FEED_TITLE}</title> + <updated>#{FEED_UPDATED}</updated> + <author> + <name>#{FEED_AUTHOR_NAME}</name> + </author> +#{block_given? ? yield : content} +</feed> +EOA + end + + def make_entry(content=nil) + <<-EOA + <entry> + <title>#{ENTRY_TITLE}</title> + <id>#{ENTRY_ID}</id> + <updated>#{ENTRY_UPDATED}</updated> +#{block_given? ? yield : content} + </entry> +EOA + end + + def make_feed_with_open_entry(content=nil, xmlns=[], &block) + make_feed_without_entry(<<-EOA, xmlns) +#{make_entry(content, &block)} +EOA + end + + def make_feed_with_open_entry_source(content=nil, xmlns=[]) + make_feed_with_open_entry(<<-EOA, xmlns) + <source> +#{block_given? ? yield : content} + </source> +EOA + end + + def make_feed(content=nil, xmlns=[]) + make_feed_without_entry(<<-EOA, xmlns) + <entry> + <title>#{ENTRY_TITLE}</title> + <link href="#{ENTRY_LINK}"/> + <id>#{ENTRY_ID}</id> + <updated>#{ENTRY_UPDATED}</updated> + <summary>#{ENTRY_SUMMARY}</summary> + </entry> +#{block_given? ? yield : content} +EOA + end + + def make_entry_document(content=nil, xmlns=[]) + <<-EOA +<entry xmlns="#{Atom::URI}" +#{xmlns.collect {|pre, uri| "xmlns:#{pre}='#{uri}'"}.join(' ')}> + <id>#{ENTRY_ID}</id> + <title>#{ENTRY_TITLE}</title> + <updated>#{ENTRY_UPDATED}</updated> + <author> + <name>#{FEED_AUTHOR_NAME}</name> + </author> +#{block_given? ? yield : content} +</entry> +EOA + end + + def make_entry_document_with_open_source(content=nil, xmlns=[]) + make_entry_document(<<-EOA, xmlns) + <source> +#{block_given? ? yield : content} + </source> +EOA + end + def make_element(elem_name, attrs, contents) attrs_str = attrs.collect do |name, value| "#{h name}='#{h value}'" @@ -215,12 +332,61 @@ EOR "<#{h elem_name} #{attrs_str}>\n#{contents_str}\n</#{h elem_name}>" end - + + def xmlns_container(xmlns_decls, content) + attributes = xmlns_decls.collect do |prefix, uri| + "xmlns:#{h prefix}=\"#{h uri}\"" + end.join(" ") + "<dummy #{attributes}>#{content}</dummy>" + end + private + def setup_rss10(rdf) + assert_equal("", rdf.to_s) + + channel = RDF::Channel.new + assert_equal("", channel.to_s) + channel.about = "http://example.com/index.rdf" + channel.title = "title" + channel.link = "http://example.com/" + channel.description = "description" + assert_equal("", channel.to_s) + + item_title = "item title" + item_link = "http://example.com/item" + channel.items = RDF::Channel::Items.new + channel.items.Seq.lis << RDF::Channel::Items::Seq::Li.new(item_link) + assert_not_equal("", channel.to_s) + + rdf.channel = channel + assert_equal("", rdf.to_s) + + item = RDF::Item.new + item.title = item_title + item.link = item_link + item.about = item_link + rdf.items << item + assert_not_equal("", rdf.to_s) + end + + def setup_rss20(rss) + assert_equal("", rss.to_s) + + channel = Rss::Channel.new + assert_equal("", channel.to_s) + channel.title = "title" + channel.link = "http://example.com/" + channel.description = "description" + assert_not_equal("", channel.to_s) + + rss.channel = channel + assert_not_equal("", rss.to_s) + end + def setup_dummy_channel(maker) about = "http://hoge.com" title = "fugafuga" - link = "http://hoge.com" + link = "http://hoge.com/feed.xml" description = "fugafugafugafuga" language = "ja" @@ -231,6 +397,17 @@ EOR maker.channel.language = language end + def setup_dummy_channel_atom(maker) + updated = Time.now + author = "Foo" + + setup_dummy_channel(maker) + maker.channel.links.first.rel = "self" + maker.channel.links.first.type = "application/atom+xml" + maker.channel.updated = updated + maker.channel.author = author + end + def setup_dummy_image(maker) title = "fugafuga" link = "http://hoge.com" @@ -262,7 +439,15 @@ EOR item.title = title item.link = link end - + + def setup_dummy_item_atom(maker) + setup_dummy_item(maker) + + item = maker.items.first + item.id = "http://example.net/xxx" + item.updated = Time.now + end + def setup_taxo_topic(target, topics) topics.each do |topic| taxo_topic = target.taxo_topics.new_taxo_topic diff --git a/test/rss/test_1.0.rb b/test/rss/test_1.0.rb index ff3c8a402e..9cb7607590 100644 --- a/test/rss/test_1.0.rb +++ b/test/rss/test_1.0.rb @@ -19,8 +19,8 @@ module RSS standalone = false rdf = RDF.new(version, encoding, standalone) - - doc = REXML::Document.new(rdf.to_s(false)) + setup_rss10(rdf) + doc = REXML::Document.new(rdf.to_s) xmldecl = doc.xml_decl @@ -59,16 +59,31 @@ module RSS link = "http://hoge.com" description = "fugafugafugafuga" resource = "http://hoge.com/hoge.png" + + item_title = "item title" + item_link = "http://hoge.com/item" + image = RDF::Channel::Image.new(resource) items = RDF::Channel::Items.new + items.Seq.lis << items.class::Seq::Li.new(item_link) textinput = RDF::Channel::Textinput.new(resource) - + + rss_item = RDF::Item.new + rss_item.title = item_title + rss_item.link = item_link + rss_item.about = item_link + channel = RDF::Channel.new(about) %w(title link description image items textinput).each do |x| channel.__send__("#{x}=", instance_eval(x)) end - doc = REXML::Document.new(make_RDF(channel.to_s)) + doc = REXML::Document.new(make_RDF(<<-EOR)) +#{channel} +<items> +#{rss_item} +</items> +EOR c = doc.root.elements[1] assert_equal(about, c.attributes["about"]) @@ -123,8 +138,12 @@ module RSS assert_equal(resource, res.value) end - def test_items + def test_channel_items + item_link = "http://example.com/item" + items = RDF::Channel::Items.new + li = items.Seq.class::Li.new(item_link) + items.Seq.lis << li doc = REXML::Document.new(make_RDF(items.to_s)) i = doc.root.elements[1] @@ -133,18 +152,34 @@ module RSS assert_equal(@uri, i.namespace) assert_equal(1, i.elements.size) - assert_equal("Seq", i.elements[1].name) - assert_equal(@rdf_uri, i.elements[1].namespace) + seq = i.elements[1] + assert_equal("Seq", seq.name) + assert_equal(@rdf_uri, seq.namespace) + + assert_equal(1, seq.elements.size) + l = seq.elements[1] + assert_equal("li", l.name) + assert_equal(@rdf_uri, l.namespace) + assert_equal(item_link, l.attributes["resource"]) end def test_seq + item_link = "http://example.com/item" seq = RDF::Seq.new + li = seq.class::Li.new(item_link) + seq.lis << li doc = REXML::Document.new(make_RDF(seq.to_s)) s = doc.root.elements[1] assert_equal("Seq", s.name) assert_equal(@rdf_uri, s.namespace) + + assert_equal(1, s.elements.size) + l = s.elements[1] + assert_equal("li", l.name) + assert_equal(@rdf_uri, l.namespace) + assert_equal(item_link, l.attributes["resource"]) end def test_li @@ -242,8 +277,20 @@ module RSS end rss09 = RSS::Parser.parse(rss09) assert_equal("0.91", rss09.rss_version) + assert_equal(["rss", "0.91", nil], rss09.feed_info) rss20 = RSS::Parser.parse(rss.to_xml("2.0")) assert_equal("2.0", rss20.rss_version) + assert_equal(["rss", "2.0", nil], rss20.feed_info) + + atom_xml = rss.to_xml("atom") do |maker| + maker.channel.author = "Alice" + maker.channel.updated ||= Time.now + maker.items.each do |item| + item.updated ||= Time.now + end + end + atom = RSS::Parser.parse(atom_xml) + assert_equal(["atom", "1.0", "feed"], atom.feed_info) end end end diff --git a/test/rss/test_2.0.rb b/test/rss/test_2.0.rb index d39c297ab7..5a63efbcb9 100644 --- a/test/rss/test_2.0.rb +++ b/test/rss/test_2.0.rb @@ -2,8 +2,6 @@ require "rexml/document" require "rss-testcase" -require "rss/2.0" - module RSS class TestRSS20Core < TestCase @@ -17,6 +15,7 @@ module RSS standalone = false rss = Rss.new(@rss_version, version, encoding, standalone) + setup_rss20(rss) doc = REXML::Document.new(rss.to_s(false)) @@ -48,7 +47,9 @@ module RSS {:href => "a.css", :type => "text/css"}, ], ].each do |attrs_ary| - assert_xml_stylesheet_pis(attrs_ary, Rss.new(@rss_version)) + rss = Rss.new(@rss_version) + setup_rss20(rss) + assert_xml_stylesheet_pis(attrs_ary, rss) end end @@ -253,6 +254,10 @@ module RSS pubDate = Time.parse("Sat, 07 Sep 2002 00:00:01 GMT") channel = Rss::Channel.new + channel.title = "title" + channel.link = "http://example.com/" + channel.description = "description" + item = Rss::Channel::Item.new channel.items << item @@ -378,13 +383,28 @@ module RSS rss = RSS::Parser.parse(make_sample_rss20) assert_equal(rss.to_s, rss.to_xml) assert_equal(rss.to_s, rss.to_xml("2.0")) - rss09 = RSS::Parser.parse(rss.to_xml("0.91")) + rss09_xml = rss.to_xml("0.91") do |maker| + setup_dummy_image(maker) + end + rss09 = RSS::Parser.parse(rss09_xml) assert_equal("0.91", rss09.rss_version) rss10 = rss.to_xml("1.0") do |maker| maker.channel.about = "http://www.example.com/index.rdf" end rss10 = RSS::Parser.parse(rss10) assert_equal("1.0", rss10.rss_version) + + atom_xml = rss.to_xml("atom1.0") do |maker| + maker.channel.id = "http://www.example.com/atom.xml" + maker.channel.author = "Alice" + maker.channel.updated = Time.now + maker.items.each do |item| + item.author = "Bob" + item.updated = Time.now + end + end + atom = RSS::Parser.parse(atom_xml) + assert_equal(["atom", "1.0", "feed"], atom.feed_info) end end end diff --git a/test/rss/test_atom.rb b/test/rss/test_atom.rb new file mode 100644 index 0000000000..c442c753b2 --- /dev/null +++ b/test/rss/test_atom.rb @@ -0,0 +1,688 @@ +require "rexml/document" + +require "rss-testcase" + +require "rss/atom" + +module RSS + class TestAtomCore < TestCase + def setup + @uri = "http://www.w3.org/2005/Atom" + @xhtml_uri = "http://www.w3.org/1999/xhtml" + end + + def test_feed + version = "1.0" + encoding = "UTF-8" + standalone = false + + feed = Atom::Feed.new(version, encoding, standalone) + assert_equal("", feed.to_s) + + author = feed.class::Author.new + name = feed.class::Author::Name.new + name.content = "an author" + author.name = name + assert_not_equal("", author.to_s) + feed.authors << author + assert_equal("", feed.to_s) + + id = feed.class::Id.new + id.content = "http://example.com/atom.xml" + assert_not_equal("", id.to_s) + feed.id = id + assert_equal("", feed.to_s) + + title = feed.class::Title.new + title.content = "a title" + assert_not_equal("", title.to_s) + feed.title = title + assert_equal("", feed.to_s) + + updated = feed.class::Updated.new + updated.content = Time.now + assert_not_equal("", updated.to_s) + feed.updated = updated + assert_not_equal("", feed.to_s) + + + feed.authors.clear + assert_equal("", feed.to_s) + entry = Atom::Feed::Entry.new + setup_entry(entry) + assert_not_equal("", entry.to_s) + + author = entry.authors.first + entry.authors.clear + assert_equal("", entry.to_s) + entry.parent = feed + assert_equal("", entry.to_s) + feed.authors << author + assert_not_equal("", entry.to_s) + feed.authors.clear + feed.entries << entry + assert_equal("", feed.to_s) + entry.authors << author + assert_not_equal("", entry.to_s) + assert_not_equal("", feed.to_s) + + doc = REXML::Document.new(feed.to_s) + xmldecl = doc.xml_decl + + %w(version encoding).each do |x| + assert_equal(instance_eval(x), xmldecl.__send__(x)) + end + assert_equal(standalone, !xmldecl.standalone.nil?) + + assert_equal(@uri, doc.root.namespace) + end + + def test_entry + version = "1.0" + encoding = "UTF-8" + standalone = false + + entry = Atom::Entry.new(version, encoding, standalone) + setup_entry(entry) + + author = entry.authors.first + entry.authors.clear + assert_equal("", entry.to_s) + source = Atom::Entry::Source.new + source.authors << author + entry.source = source + assert_not_equal("", entry.to_s) + + doc = REXML::Document.new(entry.to_s) + xmldecl = doc.xml_decl + + %w(version encoding).each do |x| + assert_equal(instance_eval(x), xmldecl.__send__(x)) + end + assert_equal(standalone, !xmldecl.standalone.nil?) + + assert_equal(@uri, doc.root.namespace) + end + + def test_not_displayed_xml_stylesheets + feed = Atom::Feed.new + plain_feed = feed.to_s + 3.times do + feed.xml_stylesheets.push(XMLStyleSheet.new) + assert_equal(plain_feed, feed.to_s) + end + end + + def test_atom_author + assert_atom_person_to_s(Atom::Feed::Author) + assert_atom_person_to_s(Atom::Feed::Entry::Author) + assert_atom_person_to_s(Atom::Entry::Author) + assert_atom_person_to_s(Atom::Feed::Entry::Source::Author) + assert_atom_person_to_s(Atom::Entry::Source::Author) + end + + def test_atom_category + assert_atom_category_to_s(Atom::Feed::Category) + assert_atom_category_to_s(Atom::Feed::Entry::Category) + assert_atom_category_to_s(Atom::Entry::Category) + assert_atom_category_to_s(Atom::Feed::Entry::Source::Category) + assert_atom_category_to_s(Atom::Entry::Source::Category) + end + + def test_atom_contributor + assert_atom_person_to_s(Atom::Feed::Contributor) + assert_atom_person_to_s(Atom::Feed::Entry::Contributor) + assert_atom_person_to_s(Atom::Entry::Contributor) + assert_atom_person_to_s(Atom::Feed::Entry::Source::Contributor) + assert_atom_person_to_s(Atom::Entry::Source::Contributor) + end + + def test_atom_generator + assert_atom_generator_to_s(Atom::Feed::Generator) + assert_atom_generator_to_s(Atom::Feed::Entry::Source::Generator) + assert_atom_generator_to_s(Atom::Entry::Source::Generator) + end + + def test_atom_icon + assert_atom_icon_to_s(Atom::Feed::Icon) + assert_atom_icon_to_s(Atom::Feed::Entry::Source::Icon) + assert_atom_icon_to_s(Atom::Entry::Source::Icon) + end + + def test_atom_id + assert_atom_id_to_s(Atom::Feed::Id) + assert_atom_id_to_s(Atom::Feed::Entry::Id) + assert_atom_id_to_s(Atom::Entry::Id) + assert_atom_id_to_s(Atom::Feed::Entry::Source::Id) + assert_atom_id_to_s(Atom::Entry::Source::Id) + end + + def test_atom_link + assert_atom_link_to_s(Atom::Feed::Link) + assert_atom_link_to_s(Atom::Feed::Entry::Link) + assert_atom_link_to_s(Atom::Entry::Link) + assert_atom_link_to_s(Atom::Feed::Entry::Source::Link) + assert_atom_link_to_s(Atom::Entry::Source::Link) + end + + def test_atom_logo + assert_atom_logo_to_s(Atom::Feed::Logo) + assert_atom_logo_to_s(Atom::Feed::Entry::Source::Logo) + assert_atom_logo_to_s(Atom::Entry::Source::Logo) + end + + def test_atom_rights + assert_atom_text_construct_to_s(Atom::Feed::Rights) + assert_atom_text_construct_to_s(Atom::Feed::Entry::Rights) + assert_atom_text_construct_to_s(Atom::Entry::Rights) + assert_atom_text_construct_to_s(Atom::Feed::Entry::Source::Rights) + assert_atom_text_construct_to_s(Atom::Entry::Source::Rights) + end + + def test_atom_subtitle + assert_atom_text_construct_to_s(Atom::Feed::Subtitle) + assert_atom_text_construct_to_s(Atom::Feed::Entry::Source::Subtitle) + assert_atom_text_construct_to_s(Atom::Entry::Source::Subtitle) + end + + def test_atom_title + assert_atom_text_construct_to_s(Atom::Feed::Title) + assert_atom_text_construct_to_s(Atom::Feed::Entry::Title) + assert_atom_text_construct_to_s(Atom::Entry::Title) + assert_atom_text_construct_to_s(Atom::Feed::Entry::Source::Title) + assert_atom_text_construct_to_s(Atom::Entry::Source::Title) + end + + def test_atom_updated + assert_atom_date_construct_to_s(Atom::Feed::Updated) + assert_atom_date_construct_to_s(Atom::Feed::Entry::Updated) + assert_atom_date_construct_to_s(Atom::Entry::Updated) + assert_atom_date_construct_to_s(Atom::Feed::Entry::Source::Updated) + assert_atom_date_construct_to_s(Atom::Entry::Source::Updated) + end + + def test_atom_content + assert_atom_content_to_s(Atom::Feed::Entry::Content) + assert_atom_content_to_s(Atom::Entry::Content) + end + + def test_atom_published + assert_atom_date_construct_to_s(Atom::Feed::Entry::Published) + assert_atom_date_construct_to_s(Atom::Entry::Published) + end + + def test_atom_summary + assert_atom_text_construct_to_s(Atom::Feed::Entry::Summary) + assert_atom_text_construct_to_s(Atom::Entry::Summary) + end + + + def test_to_xml(with_convenience_way=true) + atom = RSS::Parser.parse(make_feed) + assert_equal(atom.to_s, atom.to_xml) + assert_equal(atom.to_s, atom.to_xml("atom")) + assert_equal(atom.to_s, atom.to_xml("atom1.0")) + assert_equal(atom.to_s, atom.to_xml("atom1.0:feed")) + assert_equal(atom.to_s, atom.to_xml("atom:feed")) + + rss09_xml = atom.to_xml("0.91") do |maker| + maker.channel.language = "en-us" + maker.channel.link = "http://example.com/" + if with_convenience_way + maker.channel.description = atom.title.content + else + maker.channel.description {|d| d.content = atom.title.content} + end + + maker.image.url = "http://example.com/logo.png" + maker.image.title = "Logo" + end + rss09 = RSS::Parser.parse(rss09_xml) + assert_equal(["rss", "0.91", nil], rss09.feed_info) + + rss20_xml = atom.to_xml("2.0") do |maker| + maker.channel.link = "http://example.com/" + if with_convenience_way + maker.channel.description = atom.title.content + else + maker.channel.description {|d| d.content = atom.title.content} + end + end + rss20 = RSS::Parser.parse(rss20_xml) + assert_equal("2.0", rss20.rss_version) + assert_equal(["rss", "2.0", nil], rss20.feed_info) + end + + def test_to_xml_with_new_api_since_018 + test_to_xml(false) + end + + private + def setup_entry(entry) + _wrap_assertion do + assert_equal("", entry.to_s) + + author = entry.class::Author.new + name = entry.class::Author::Name.new + name.content = "an author" + author.name = name + assert_not_equal("", author.to_s) + entry.authors << author + assert_equal("", entry.to_s) + + id = entry.class::Id.new + id.content = "http://example.com/atom.xml" + assert_not_equal("", id.to_s) + entry.id = id + assert_equal("", entry.to_s) + + title = entry.class::Title.new + title.content = "a title" + assert_not_equal("", title.to_s) + entry.title = title + assert_equal("", entry.to_s) + + updated = entry.class::Updated.new + updated.content = Time.now + assert_not_equal("", updated.to_s) + entry.updated = updated + assert_not_equal("", entry.to_s) + end + end + + + def assert_atom_person_to_s(target_class) + _wrap_assertion do + name = "A person" + uri = "http://example.com/person/" + email = "person@example.com" + + target = target_class.new + assert_equal("", target.to_s) + + target = target_class.new + person_name = target_class::Name.new + person_name.content = name + target.name = person_name + xml_target = REXML::Document.new(target.to_s).root + assert_equal(["name"], xml_target.elements.collect {|e| e.name}) + assert_equal([name], xml_target.elements.collect {|e| e.text}) + + person_uri = target_class::Uri.new + person_uri.content = uri + target.uri = person_uri + xml_target = REXML::Document.new(target.to_s).root + assert_equal(["name", "uri"], xml_target.elements.collect {|e| e.name}) + assert_equal([name, uri], xml_target.elements.collect {|e| e.text}) + + person_email = target_class::Email.new + person_email.content = email + target.email = person_email + xml_target = REXML::Document.new(target.to_s).root + assert_equal(["name", "uri", "email"], + xml_target.elements.collect {|e| e.name}) + assert_equal([name, uri, email], + xml_target.elements.collect {|e| e.text}) + end + end + + def assert_atom_category_to_s(target_class) + _wrap_assertion do + term = "music" + scheme = "http://example.com/music" + label = "Music" + + category = target_class.new + assert_equal("", category.to_s) + + category = target_class.new + category.scheme = scheme + assert_equal("", category.to_s) + + category = target_class.new + category.label = label + assert_equal("", category.to_s) + + category = target_class.new + category.scheme = scheme + category.label = label + assert_equal("", category.to_s) + + category = target_class.new + category.term = term + xml = REXML::Document.new(category.to_s).root + assert_rexml_element([], {"term" => term}, nil, xml) + + category = target_class.new + category.term = term + category.scheme = scheme + xml = REXML::Document.new(category.to_s).root + assert_rexml_element([], {"term" => term, "scheme" => scheme}, nil, xml) + + category = target_class.new + category.term = term + category.label = label + xml = REXML::Document.new(category.to_s).root + assert_rexml_element([], {"term" => term, "label" => label}, nil, xml) + + category = target_class.new + category.term = term + category.scheme = scheme + category.label = label + xml = REXML::Document.new(category.to_s).root + attrs = {"term" => term, "scheme" => scheme, "label" => label} + assert_rexml_element([], attrs, nil, xml) + end + end + + def assert_atom_generator_to_s(target_class) + _wrap_assertion do + content = "Feed generator" + uri = "http://example.com/generator" + version = "0.0.1" + + generator = target_class.new + assert_equal("", generator.to_s) + + generator = target_class.new + generator.uri = uri + assert_equal("", generator.to_s) + + generator = target_class.new + generator.version = version + assert_equal("", generator.to_s) + + generator = target_class.new + generator.uri = uri + generator.version = version + assert_equal("", generator.to_s) + + generator = target_class.new + generator.content = content + xml = REXML::Document.new(generator.to_s).root + assert_rexml_element([], {}, content, xml) + + generator = target_class.new + generator.content = content + generator.uri = uri + xml = REXML::Document.new(generator.to_s).root + assert_rexml_element([], {"uri" => uri}, content, xml) + + generator = target_class.new + generator.content = content + generator.version = version + xml = REXML::Document.new(generator.to_s).root + assert_rexml_element([], {"version" => version}, content, xml) + + generator = target_class.new + generator.content = content + generator.uri = uri + generator.version = version + xml = REXML::Document.new(generator.to_s).root + assert_rexml_element([], {"uri" => uri, "version" => version}, + content, xml) + end + end + + def assert_atom_icon_to_s(target_class) + _wrap_assertion do + content = "http://example.com/icon.png" + + icon = target_class.new + assert_equal("", icon.to_s) + + icon = target_class.new + icon.content = content + xml = REXML::Document.new(icon.to_s).root + assert_rexml_element([], {}, content, xml) + end + end + + def assert_atom_id_to_s(target_class) + _wrap_assertion do + content = "http://example.com/1" + + id = target_class.new + assert_equal("", id.to_s) + + id = target_class.new + id.content = content + xml = REXML::Document.new(id.to_s).root + assert_rexml_element([], {}, content, xml) + end + end + + def assert_atom_link_to_s(target_class) + _wrap_assertion do + href = "http://example.com/atom.xml" + rel = "self" + type = "application/atom+xml" + hreflang = "ja" + title = "Atom Feed" + length = "801" + + link = target_class.new + assert_equal("", link.to_s) + + link = target_class.new + link.href = href + xml = REXML::Document.new(link.to_s).root + assert_rexml_element([], {"href" => href}, nil, xml) + + optional_arguments = %w(rel type hreflang title length) + optional_arguments.each do |name| + rest = optional_arguments.reject {|x| x == name} + + link = target_class.new + link.__send__("#{name}=", eval(name)) + assert_equal("", link.to_s) + + rest.each do |n| + link.__send__("#{n}=", eval(n)) + assert_equal("", link.to_s) + end + + link = target_class.new + link.href = href + link.__send__("#{name}=", eval(name)) + attrs = [["href", href], [name, eval(name)]] + xml = REXML::Document.new(link.to_s).root + assert_rexml_element([], attrs, nil, xml) + + rest.each do |n| + link.__send__("#{n}=", eval(n)) + attrs << [n, eval(n)] + xml = REXML::Document.new(link.to_s).root + assert_rexml_element([], attrs, nil, xml) + end + end + end + end + + def assert_atom_logo_to_s(target_class) + _wrap_assertion do + content = "http://example.com/logo.png" + + logo = target_class.new + assert_equal("", logo.to_s) + + logo = target_class.new + logo.content = content + xml = REXML::Document.new(logo.to_s).root + assert_rexml_element([], {}, content, xml) + end + end + + def assert_atom_text_construct_to_s(target_class) + _wrap_assertion do + text_content = "plain text" + html_content = "<em>#{text_content}</em>" + xhtml_uri = "http://www.w3.org/1999/xhtml" + xhtml_em = RSS::XML::Element.new("em", nil, xhtml_uri, {}, text_content) + xhtml_content = RSS::XML::Element.new("div", nil, xhtml_uri, + {"xmlns" => xhtml_uri}, + [xhtml_em]) + + text = target_class.new + assert_equal("", text.to_s) + + text = target_class.new + text.type = "text" + assert_equal("", text.to_s) + + text = target_class.new + text.content = text_content + xml = REXML::Document.new(text.to_s).root + assert_rexml_element([], {}, text_content, xml) + + text = target_class.new + text.type = "text" + text.content = text_content + xml = REXML::Document.new(text.to_s).root + assert_rexml_element([], {"type" => "text"}, text_content, xml) + + text = target_class.new + text.type = "html" + text.content = html_content + xml = REXML::Document.new(text.to_s).root + assert_rexml_element([], {"type" => "html"}, html_content, xml) + + text = target_class.new + text.type = "xhtml" + text.content = xhtml_content + assert_equal("", text.to_s) + + text = target_class.new + text.type = "xhtml" + text.__send__(target_class.xml_setter, xhtml_content) + xml = REXML::Document.new(text.to_s).root + assert_rexml_element([[xhtml_uri, "div"]], {"type" => "xhtml"}, + nil, xml) + assert_rexml_element([[xhtml_uri, "em"]], nil, nil, xml.elements[1]) + assert_rexml_element([], {}, text_content, xml.elements[1].elements[1]) + + text = target_class.new + text.type = "xhtml" + text.__send__(target_class.xml_setter, xhtml_em) + xml = REXML::Document.new(text.to_s).root + assert_rexml_element([[xhtml_uri, "div"]], {"type" => "xhtml"}, + nil, xml) + assert_rexml_element([[xhtml_uri, "em"]], nil, nil, xml.elements[1]) + assert_rexml_element([], {}, text_content, xml.elements[1].elements[1]) + end + end + + def assert_atom_date_construct_to_s(target_class) + _wrap_assertion do + date = target_class.new + assert_equal("", date.to_s) + + [ + "2003-12-13T18:30:02Z", + "2003-12-13T18:30:02.25Z", + "2003-12-13T18:30:02+01:00", + "2003-12-13T18:30:02.25+01:00", + ].each do |content| + date = target_class.new + date.content = content + xml = REXML::Document.new(date.to_s).root + assert_rexml_element([], {}, content, xml, :time) + + date = target_class.new + date.content = Time.parse(content) + xml = REXML::Document.new(date.to_s).root + assert_rexml_element([], {}, content, xml, :time) + end + end + end + + def assert_atom_content_to_s(target_class) + _wrap_assertion do + assert_atom_text_construct_to_s(target_class) + assert_atom_content_inline_other_xml_to_s(target_class) + assert_atom_content_inline_other_text_to_s(target_class) + assert_atom_content_inline_other_base64_to_s(target_class) + assert_atom_content_out_of_line_to_s(target_class) + end + end + + def assert_atom_content_inline_other_xml_to_s(target_class) + _wrap_assertion do + content = target_class.new + content.type = "text/xml" + assert_equal("", content.to_s) + + content = target_class.new + content.type = "text/xml" + content.xml = RSS::XML::Element.new("em") + xml = REXML::Document.new(content.to_s).root + assert_rexml_element([["", "em"]], {"type" => "text/xml"}, nil, xml) + end + end + + def assert_atom_content_inline_other_text_to_s(target_class) + _wrap_assertion do + content = target_class.new + content.type = "text/plain" + assert_equal("", content.to_s) + + content = target_class.new + content.type = "text/plain" + content.xml = RSS::XML::Element.new("em") + assert_equal("", content.to_s) + + content = target_class.new + content.type = "text/plain" + content.content = "content" + xml = REXML::Document.new(content.to_s).root + assert_rexml_element([], {"type" => "text/plain"}, "content", xml) + end + end + + def assert_atom_content_inline_other_base64_to_s(target_class) + _wrap_assertion do + require "zlib" + + text = "" + char = "a" + 100.times do |i| + text << char + char.succ! + end + + type = "application/zip" + original_content = Zlib::Deflate.deflate(text) + + content = target_class.new + content.type = type + content.content = original_content + xml = REXML::Document.new(content.to_s).root + assert_rexml_element([], {"type" => type}, + Base64.encode64(original_content), xml) + end + end + + def assert_atom_content_out_of_line_to_s(target_class) + _wrap_assertion do + type = "application/zip" + src = "http://example.com/xxx.zip" + + content = target_class.new + assert(!content.out_of_line?) + content.src = src + assert(content.out_of_line?) + xml = REXML::Document.new(content.to_s).root + assert_rexml_element([], {"src" => src}, nil, xml) + + content = target_class.new + assert(!content.out_of_line?) + content.type = type + assert(!content.out_of_line?) + content.src = src + assert(content.out_of_line?) + xml = REXML::Document.new(content.to_s).root + assert_rexml_element([], {"type" => type, "src" => src}, nil, xml) + end + end + end +end diff --git a/test/rss/test_dublincore.rb b/test/rss/test_dublincore.rb index e5a4919362..22b81483f4 100644 --- a/test/rss/test_dublincore.rb +++ b/test/rss/test_dublincore.rb @@ -8,86 +8,168 @@ require "rss/dublincore" module RSS class TestDublinCore < TestCase - def setup - @prefix = "dc" - @uri = "http://purl.org/dc/elements/1.1/" - - @parents = %w(channel image item textinput) - - t = Time.iso8601("2000-01-01T12:00:05+00:00") - class << t - alias_method(:to_s, :iso8601) - end - - @elems = { - :title => "hoge", - :description => - " XML is placing increasingly heavy loads on - the existing technical infrastructure of the Internet.", - :creator => "Rael Dornfest (mailto:rael@oreilly.com)", - :subject => "XML", - :publisher => "The O'Reilly Network", - :contributor => "hogehoge", - :type => "fugafuga", - :format => "hohoho", - :identifier => "fufufu", - :source => "barbar", - :language => "ja", - :relation => "cococo", - :rights => "Copyright (c) 2000 O'Reilly & Associates, Inc.", - :date => t, - } - - @dc_nodes = @elems.collect do |name, value| - "<#{@prefix}:#{name}>#{value}</#{@prefix}:#{name}>" - end.join("\n") - - @rss_source = make_RDF(<<-EOR, {@prefix => @uri}) -#{make_channel(@dc_nodes)} -#{make_image(@dc_nodes)} -#{make_item(@dc_nodes)} -#{make_textinput(@dc_nodes)} + @rss10_parents = [%w(channel), %w(image), %w(item), %w(textinput)] + + @rss10_source = make_RDF(<<-EOR, {DC_PREFIX => DC_URI}) +#{make_channel(DC_NODES)} +#{make_image(DC_NODES)} +#{make_item(DC_NODES)} +#{make_textinput(DC_NODES)} +EOR + + @rss20_parents = [%w(channel), %w(items last)] + + @rss20_source = make_rss20(<<-EOR, {DC_PREFIX => DC_URI}) +#{make_channel20(DC_NODES + make_item20(DC_NODES))} +EOR + + @atom_feed_parents = [[], %w(entries last)] + + @atom_feed_source = make_feed(<<-EOR, {DC_PREFIX => DC_URI}) +#{DC_NODES} +#{make_entry(DC_NODES)} EOR - @rss = Parser.parse(@rss_source) + @atom_entry_parents = [[]] + + @atom_entry_source = make_entry_document(<<-EOR, {DC_PREFIX => DC_URI}) +#{DC_NODES} +EOR end - + def test_parser - assert_nothing_raised do - Parser.parse(@rss_source) + rss10_maker = Proc.new do |content, xmlns| + make_RDF(<<-EOR, xmlns) +#{make_channel(content)} +#{make_image(content)} +#{make_item(content)} +#{make_textinput(content)} +EOR + end + assert_dc_parse(@rss10_source, @rss10_parents, false, &rss10_maker) + assert_dc_parse(@rss10_source, @rss10_parents, true, &rss10_maker) + + rss20_maker = Proc.new do |content, xmlns| + make_rss20(<<-EOR, xmlns) +#{make_channel20(content + make_item20(content))} +EOR + end + assert_dc_parse(@rss20_source, @rss20_parents, false, &rss20_maker) + assert_dc_parse(@rss20_source, @rss20_parents, true, &rss20_maker) + + atom_feed_maker = Proc.new do |content, xmlns| + make_feed(<<-EOR, xmlns) +#{content} +#{make_entry(content)} +EOR end - - @elems.each do |tag, value| - rss = nil - assert_nothing_raised do - rss = Parser.parse(make_RDF(<<-EOR, {@prefix => @uri})) -#{make_channel(("<" + @prefix + ":" + tag.to_s + ">" + - value.to_s + - "</" + @prefix + ":" + tag.to_s + ">") * 2)} -#{make_item} + assert_dc_parse(@atom_feed_source, @atom_feed_parents, false, + &atom_feed_maker) + assert_dc_parse(@atom_feed_source, @atom_feed_parents, true, + &atom_feed_maker) + + atom_entry_maker = Proc.new do |content, xmlns| + make_entry_document(<<-EOR, xmlns) +#{content} EOR + end + assert_dc_parse(@atom_entry_source, @atom_entry_parents, false, + &atom_entry_maker) + assert_dc_parse(@atom_entry_source, @atom_entry_parents, true, + &atom_entry_maker) + end + + def test_singular_accessor + assert_dc_singular_accessor(@rss10_source, @rss10_parents) + assert_dc_singular_accessor(@rss20_source, @rss20_parents) + assert_dc_singular_accessor(@atom_feed_source, @atom_feed_parents) + assert_dc_singular_accessor(@atom_entry_source, @atom_entry_parents) + end + + def test_plural_accessor + assert_dc_plural_accessor(@rss10_source, @rss10_parents, false) + assert_dc_plural_accessor(@rss10_source, @rss10_parents, true) + + assert_dc_plural_accessor(@rss20_source, @rss20_parents, false) + assert_dc_plural_accessor(@rss20_source, @rss20_parents, true) + + assert_dc_plural_accessor(@atom_feed_source, @atom_feed_parents, false) + assert_dc_plural_accessor(@atom_feed_source, @atom_feed_parents, true) + + assert_dc_plural_accessor(@atom_entry_source, @atom_entry_parents, false) + assert_dc_plural_accessor(@atom_entry_source, @atom_entry_parents, true) + end + + def test_to_s + assert_dc_to_s(@rss10_source, @rss10_parents, false) + assert_dc_to_s(@rss10_source, @rss10_parents, true) + + targets = ["channel", "channel/item[3]"] + assert_dc_to_s(@rss20_source, @rss20_parents, false, targets) + assert_dc_to_s(@rss20_source, @rss20_parents, true, targets) + + targets = [".", "entry"] + assert_dc_to_s(@atom_feed_source, @atom_feed_parents, false, targets) + assert_dc_to_s(@atom_feed_source, @atom_feed_parents, true, targets) + + targets = ["."] + assert_dc_to_s(@atom_entry_source, @atom_entry_parents, false, targets) + assert_dc_to_s(@atom_entry_source, @atom_entry_parents, true, targets) + end + + private + def dc_plural_suffix(name, check_backward_compatibility) + if name == :rights + if check_backward_compatibility + "es" + else + "_list" end - plural_reader = "dc_#{tag}" + (tag == :rights ? "es" : "s") - values = rss.channel.__send__(plural_reader).collect do |x| - val = x.value - if val.kind_of?(String) - CGI.escapeHTML(val) - else - val + else + "s" + end + end + + def assert_dc_parse(source, parents, check_backward_compatibility, &maker) + assert_nothing_raised do + Parser.parse(source) + end + + DC_ELEMENTS.each do |name, value| + parents.each do |parent_readers| + feed = nil + assert_nothing_raised do + tag = "#{DC_PREFIX}:#{name}" + dc_content = "<#{tag}>#{value}</#{tag}>\n" + dc_content *= 2 + feed = Parser.parse(maker.call(dc_content, {DC_PREFIX => DC_URI})) end + parent = chain_reader(feed, parent_readers) + + plural_suffix = dc_plural_suffix(name, check_backward_compatibility) + plural_reader = "dc_#{name}#{plural_suffix}" + values = parent.__send__(plural_reader).collect do |x| + val = x.value + if val.kind_of?(String) + CGI.escapeHTML(val) + else + val + end + end + assert_equal([value, value], values) end - assert_equal([value, value], values) end - end - def test_singular_accessor + def assert_dc_singular_accessor(source, parents) + feed = Parser.parse(source) new_value = "hoge" - @elems.each do |name, value| - @parents.each do |parent| - parsed_value = @rss.__send__(parent).__send__("dc_#{name}") + parents.each do |parent_readers| + parent = chain_reader(feed, parent_readers) + DC_ELEMENTS.each do |name, value| + parsed_value = parent.__send__("dc_#{name}") if parsed_value.kind_of?(String) parsed_value = CGI.escapeHTML(parsed_value) end @@ -97,34 +179,40 @@ EOR class << t alias_method(:to_s, :iso8601) end - @rss.__send__(parent).__send__("dc_#{name}=", t.iso8601) - assert_equal(t, @rss.__send__(parent).__send__("dc_#{name}")) - assert_equal(t, @rss.__send__(parent).date) - - @rss.__send__(parent).date = value - assert_equal(value, @rss.__send__(parent).date) - assert_equal(value, @rss.__send__(parent).__send__("dc_#{name}")) + parent.__send__("dc_#{name}=", t.iso8601) + assert_equal(t, parent.__send__("dc_#{name}")) + if parent.class.method_defined?(:date_without_dc_date=) + assert_nil(parent.date) + else + assert_equal(t, parent.date) + end + + parent.date = value + assert_equal(value, parent.date) + assert_equal(value, parent.__send__("dc_#{name}")) else - @rss.__send__(parent).__send__("dc_#{name}=", new_value) - assert_equal(new_value, - @rss.__send__(parent).__send__("dc_#{name}")) + parent.__send__("dc_#{name}=", new_value) + assert_equal(new_value, parent.__send__("dc_#{name}")) end end end end - def test_plural_accessor + def assert_dc_plural_accessor(source, parents, check_backward_compatibility) + feed = Parser.parse(source) new_value = "hoge" - - @elems.each do |name, value| - @parents.each do |parent| - parsed_value = @rss.__send__(parent).__send__("dc_#{name}") + + DC_ELEMENTS.each do |name, value| + parents.each do |parent_readers| + parent = chain_reader(feed, parent_readers) + parsed_value = parent.__send__("dc_#{name}") if parsed_value.kind_of?(String) parsed_value = CGI.escapeHTML(parsed_value) end assert_equal(value, parsed_value) - plural_reader = "dc_#{name}" + (name == :rights ? "es" : "s") + plural_suffix = dc_plural_suffix(name, check_backward_compatibility) + plural_reader = "dc_#{name}#{plural_suffix}" klass_name = "DublinCore#{Utils.to_class_name(name.to_s)}" klass = DublinCoreModel.const_get(klass_name) if name == :date @@ -132,58 +220,60 @@ EOR class << t alias_method(:to_s, :iso8601) end - elems = @rss.__send__(parent).__send__(plural_reader) + elems = parent.__send__(plural_reader) elems << klass.new(t.iso8601) - new_elems = @rss.__send__(parent).__send__(plural_reader) + new_elems = parent.__send__(plural_reader) values = new_elems.collect{|x| x.value} - assert_equal([@rss.__send__(parent).__send__("dc_#{name}"), t], - values) + assert_equal([parent.__send__("dc_#{name}"), t], values) else - elems = @rss.__send__(parent).__send__(plural_reader) + elems = parent.__send__(plural_reader) elems << klass.new(new_value) - new_elems = @rss.__send__(parent).__send__(plural_reader) + new_elems = parent.__send__(plural_reader) values = new_elems.collect{|x| x.value} - assert_equal([ - @rss.__send__(parent).__send__("dc_#{name}"), - new_value - ], + assert_equal([parent.__send__("dc_#{name}"), new_value], values) end end end end - def test_to_s - @elems.each do |name, value| - excepted = "<#{@prefix}:#{name}>#{value}</#{@prefix}:#{name}>" - @parents.each do |parent| - assert_equal(excepted, - @rss.__send__(parent).__send__("dc_#{name}_elements")) + def assert_dc_to_s(source, parents, check_backward_compatibility, + targets=nil) + feed = Parser.parse(source) + + DC_ELEMENTS.each do |name, value| + excepted = "<#{DC_PREFIX}:#{name}>#{value}</#{DC_PREFIX}:#{name}>" + parents.each do |parent_readers| + parent = chain_reader(feed, parent_readers) + assert_equal(excepted, parent.__send__("dc_#{name}_elements")) end - + + plural_suffix = dc_plural_suffix(name, check_backward_compatibility) + reader = "dc_#{name}#{plural_suffix}" excepted = Array.new(2, excepted).join("\n") - @parents.each do |parent| - reader = "dc_#{name}" + (name == :rights ? "es" : "s") - elems = @rss.__send__(parent).__send__(reader) + parents.each do |parent_readers| + parent = chain_reader(feed, parent_readers) + elems = parent.__send__(reader) klass_name = "DublinCore#{Utils.to_class_name(name.to_s)}" klass = DublinCoreModel.const_get(klass_name) - elems << klass.new(@rss.__send__(parent).__send__("dc_#{name}")) - assert_equal(excepted, - @rss.__send__(parent).__send__("dc_#{name}_elements")) + elems << klass.new(parent.__send__("dc_#{name}")) + assert_equal(excepted, parent.__send__("dc_#{name}_elements")) end end - - REXML::Document.new(@rss_source).root.each_element do |parent| - if @parents.include?(parent.name) - parent.each_element do |elem| - if elem.namespace == @uri - assert_equal(CGI.escapeHTML(elem.text), - @elems[elem.name.intern].to_s) - end + + targets ||= parents.collect do |parent_readers| + parent_readers.first + end + feed_root = REXML::Document.new(source).root + targets.each do |target_xpath| + parent = feed_root.elements[target_xpath] + parent.each_element do |elem| + if elem.namespace == DC_URI + assert_equal(CGI.escapeHTML(elem.text), + DC_ELEMENTS[elem.name.intern].to_s) end end end end - end end diff --git a/test/rss/test_image.rb b/test/rss/test_image.rb index 241fdafb7d..101b7ffda2 100644 --- a/test/rss/test_image.rb +++ b/test/rss/test_image.rb @@ -11,7 +11,7 @@ module RSS def setup @prefix = "image" - @uri = "http://web.resource.org/rss/1.0/modules/image/" + @uri = "http://purl.org/rss/1.0/modules/image/" @favicon_attrs = { "rdf:about" => "http://www.kuro5hin.org/favicon.ico", @@ -65,20 +65,30 @@ 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, @ns)) #{make_channel(@channel_nodes * 2)} #{make_item} EOR end + + attrs = {"rdf:about" => "http://www.example.org/item.png"} + contents = [["#{@prefix}:width", "80"]] * 5 + image_item = make_element("#{@prefix}:item", attrs, contents) + assert_too_much_tag("width", "item") do + Parser.parse(make_RDF(<<-EOR, @ns)) +#{make_channel} +#{make_item(image_item)} +EOR + end end - + def test_favicon_accessor favicon = @rss.channel.image_favicon [ @@ -135,7 +145,7 @@ EOR 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"], @@ -163,22 +173,26 @@ EOR 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, "")) + expected_xml = image_xmlns_container(make_element("#{@prefix}:favicon", + @favicon_attrs, + @favicon_contents)) + expected = REXML::Document.new(expected_xml) + actual_xml = image_xmlns_container(favicon.to_s(false, "")) + actual = REXML::Document.new(actual_xml) 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, "")) + expected_xml = make_element("#{@prefix}:item", attrs, contents) + expected_xml = image_xmlns_container(expected_xml) + expected = REXML::Document.new(expected_xml) + actual_xml = image_xmlns_container(item.image_item.to_s(false, "")) + actual = REXML::Document.new(actual_xml) 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}"] @@ -187,5 +201,14 @@ EOR end end + private + def image_xmlns_container(content) + xmlns_container({ + @prefix => @uri, + "dc" => "http://purl.org/dc/elements/1.1/", + "rdf" => "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + }, + content) + end end end diff --git a/test/rss/test_inherit.rb b/test/rss/test_inherit.rb index fc4bbbe76a..f73096f478 100644 --- a/test/rss/test_inherit.rb +++ b/test/rss/test_inherit.rb @@ -9,7 +9,7 @@ module RSS def self.indent_size; 1; end def self.tag_name; 'image'; end end - + def setup @rss = make_RDF(<<-EOR) #{make_channel} @@ -18,7 +18,7 @@ module RSS #{make_textinput} EOR end - + def test_inherit rss = RSS::Parser.parse(@rss) orig_image = rss.image @@ -36,6 +36,5 @@ EOR assert_equal("#{prefix} #{orig_image.url}", new_image.url) assert_equal("#{prefix} #{orig_image.link}", new_image.link) end - end end diff --git a/test/rss/test_itunes.rb b/test/rss/test_itunes.rb new file mode 100644 index 0000000000..3663e2c77a --- /dev/null +++ b/test/rss/test_itunes.rb @@ -0,0 +1,347 @@ +require "cgi" +require "rexml/document" + +require "rss-testcase" + +require "rss/2.0" +require "rss/itunes" + +module RSS + class TestITunes < TestCase + def test_author + assert_itunes_author(%w(channel)) do |content, xmlns| + make_rss20(make_channel20(content), xmlns) + end + + assert_itunes_author(%w(items last)) do |content, xmlns| + make_rss20(make_channel20(make_item20(content)), xmlns) + end + end + + def test_block + assert_itunes_block(%w(items last)) do |content, xmlns| + make_rss20(make_channel20(make_item20(content)), xmlns) + end + end + + def test_category + assert_itunes_category(%w(channel)) do |content, xmlns| + make_rss20(make_channel20(content), xmlns) + end + end + + def test_image + assert_itunes_image(%w(channel)) do |content, xmlns| + make_rss20(make_channel20(content), xmlns) + end + end + + def test_duration + assert_itunes_duration(%w(items last)) do |content, xmlns| + make_rss20(make_channel20(make_item20(content)), xmlns) + end + end + + def test_explicit + assert_itunes_explicit(%w(channel)) do |content, xmlns| + make_rss20(make_channel20(content), xmlns) + end + + assert_itunes_explicit(%w(items last)) do |content, xmlns| + make_rss20(make_channel20(make_item20(content)), xmlns) + end + end + + def test_keywords + assert_itunes_keywords(%w(channel)) do |content, xmlns| + make_rss20(make_channel20(content), xmlns) + end + + assert_itunes_keywords(%w(items last)) do |content, xmlns| + make_rss20(make_channel20(make_item20(content)), xmlns) + end + end + + def test_new_feed_url + assert_itunes_new_feed_url(%w(channel)) do |content, xmlns| + make_rss20(make_channel20(content), xmlns) + end + end + + def test_owner + assert_itunes_owner(%w(channel)) do |content, xmlns| + make_rss20(make_channel20(content), xmlns) + end + end + + def test_subtitle + assert_itunes_subtitle(%w(channel)) do |content, xmlns| + make_rss20(make_channel20(content), xmlns) + end + + assert_itunes_subtitle(%w(items last)) do |content, xmlns| + make_rss20(make_channel20(make_item20(content)), xmlns) + end + end + + def test_summary + assert_itunes_summary(%w(channel)) do |content, xmlns| + make_rss20(make_channel20(content), xmlns) + end + + assert_itunes_summary(%w(items last)) do |content, xmlns| + make_rss20(make_channel20(make_item20(content)), xmlns) + end + end + + private + def itunes_rss20_parse(content, &maker) + xmlns = {"itunes" => "http://www.itunes.com/dtds/podcast-1.0.dtd"} + rss20_xml = maker.call(content, xmlns) + ::RSS::Parser.parse(rss20_xml) + end + + def assert_itunes_author(readers, &rss20_maker) + _wrap_assertion do + author = "John Lennon" + rss20 = itunes_rss20_parse(tag("itunes:author", author), &rss20_maker) + target = chain_reader(rss20, readers) + assert_equal(author, target.itunes_author) + end + end + + def _assert_itunes_block(value, boolean_value, readers, &rss20_maker) + rss20 = itunes_rss20_parse(tag("itunes:block", value), &rss20_maker) + target = chain_reader(rss20, readers) + assert_equal(value, target.itunes_block) + assert_equal(boolean_value, target.itunes_block?) + end + + def assert_itunes_block(readers, &rss20_maker) + _wrap_assertion do + _assert_itunes_block("yes", true, readers, &rss20_maker) + _assert_itunes_block("Yes", true, readers, &rss20_maker) + _assert_itunes_block("no", false, readers, &rss20_maker) + _assert_itunes_block("", false, readers, &rss20_maker) + end + end + + def _assert_itunes_category(categories, readers, &rss20_maker) + cats = categories.collect do |category| + if category.is_a?(Array) + category, sub_category = category + tag("itunes:category", + tag("itunes:category", nil, {"text" => sub_category}), + {"text" => category}) + else + tag("itunes:category", nil, {"text" => category}) + end + end.join + rss20 = itunes_rss20_parse(cats, &rss20_maker) + target = chain_reader(rss20, readers) + actual_categories = target.itunes_categories.collect do |category| + cat = category.text + if category.itunes_categories.empty? + cat + else + [cat, *category.itunes_categories.collect {|c| c.text}] + end + end + assert_equal(categories, actual_categories) + end + + def assert_itunes_category(readers, &rss20_maker) + _wrap_assertion do + _assert_itunes_category(["Audio Blogs"], readers, &rss20_maker) + _assert_itunes_category([["Arts & Entertainment", "Games"]], + readers, &rss20_maker) + _assert_itunes_category([["Arts & Entertainment", "Games"], + ["Technology", "Computers"], + "Audio Blogs"], + readers, &rss20_maker) + end + end + + def assert_itunes_image(readers, &rss20_maker) + _wrap_assertion do + url = "http://example.com/podcasts/everything/AllAboutEverything.jpg" + content = tag("itunes:image", nil, {"href" => url}) + rss20 = itunes_rss20_parse(content, &rss20_maker) + target = chain_reader(rss20, readers) + assert_not_nil(target.itunes_image) + assert_equal(url, target.itunes_image.href) + + assert_missing_attribute("image", "href") do + content = tag("itunes:image") + itunes_rss20_parse(content, &rss20_maker) + end + end + end + + def _assert_itunes_duration(hour, minute, second, value, + readers, &rss20_maker) + content = tag("itunes:duration", value) + rss20 = itunes_rss20_parse(content, &rss20_maker) + duration = chain_reader(rss20, readers).itunes_duration + assert_equal(value, duration.content) + assert_equal(hour, duration.hour) + assert_equal(minute, duration.minute) + assert_equal(second, duration.second) + end + + def _assert_itunes_duration_not_available_value(value, &rss20_maker) + assert_not_available_value("duration", value) do + content = tag("itunes:duration", value) + itunes_rss20_parse(content, &rss20_maker) + end + end + + def assert_itunes_duration(readers, &rss20_maker) + _wrap_assertion do + _assert_itunes_duration(7, 14, 5, "07:14:05", readers, &rss20_maker) + _assert_itunes_duration(7, 14, 5, "7:14:05", readers, &rss20_maker) + _assert_itunes_duration(0, 4, 55, "04:55", readers, &rss20_maker) + _assert_itunes_duration(0, 4, 5, "4:05", readers, &rss20_maker) + + _assert_itunes_duration_not_available_value("5", &rss20_maker) + _assert_itunes_duration_not_available_value("09:07:14:05", &rss20_maker) + _assert_itunes_duration_not_available_value("10:5", &rss20_maker) + _assert_itunes_duration_not_available_value("10:03:5", &rss20_maker) + _assert_itunes_duration_not_available_value("10:3:05", &rss20_maker) + + _assert_itunes_duration_not_available_value("xx:xx:xx", &rss20_maker) + end + end + + def _assert_itunes_explicit(explicit, value, readers, &rss20_maker) + content = tag("itunes:explicit", value) + rss20 = itunes_rss20_parse(content, &rss20_maker) + target = chain_reader(rss20, readers) + assert_equal(value, target.itunes_explicit) + assert_equal(explicit, target.itunes_explicit?) + end + + def assert_itunes_explicit(readers, &rss20_maker) + _wrap_assertion do + _assert_itunes_explicit(true, "yes", readers, &rss20_maker) + _assert_itunes_explicit(false, "clean", readers, &rss20_maker) + _assert_itunes_explicit(nil, "no", readers, &rss20_maker) + end + end + + def _assert_itunes_keywords(keywords, value, readers, &rss20_maker) + content = tag("itunes:keywords", value) + rss20 = itunes_rss20_parse(content, &rss20_maker) + target = chain_reader(rss20, readers) + assert_equal(keywords, target.itunes_keywords) + end + + def assert_itunes_keywords(readers, &rss20_maker) + _wrap_assertion do + _assert_itunes_keywords(["salt"], "salt", readers, &rss20_maker) + _assert_itunes_keywords(["salt"], " salt ", readers, &rss20_maker) + _assert_itunes_keywords(["salt", "pepper", "shaker", "exciting"], + "salt, pepper, shaker, exciting", + readers, &rss20_maker) + _assert_itunes_keywords(["metric", "socket", "wrenches", "toolsalt"], + "metric, socket, wrenches, toolsalt", + readers, &rss20_maker) + _assert_itunes_keywords(["olitics", "red", "blue", "state"], + "olitics, red, blue, state", + readers, &rss20_maker) + end + end + + def assert_itunes_new_feed_url(readers, &rss20_maker) + _wrap_assertion do + url = "http://newlocation.com/example.rss" + content = tag("itunes:new-feed-url", url) + rss20 = itunes_rss20_parse(content, &rss20_maker) + target = chain_reader(rss20, readers) + assert_equal(url, target.itunes_new_feed_url) + end + end + + def _assert_itunes_owner(name, email, readers, &rss20_maker) + content = tag("itunes:owner", + tag("itunes:name", name) + tag("itunes:email", email)) + rss20 = itunes_rss20_parse(content, &rss20_maker) + owner = chain_reader(rss20, readers).itunes_owner + assert_equal(name, owner.itunes_name) + assert_equal(email, owner.itunes_email) + end + + def assert_itunes_owner(readers, &rss20_maker) + _wrap_assertion do + _assert_itunes_owner("John Doe", "john.doe@example.com", + readers, &rss20_maker) + + assert_missing_tag("name", "owner") do + content = tag("itunes:owner") + itunes_rss20_parse(content, &rss20_maker) + end + + assert_missing_tag("name", "owner") do + content = tag("itunes:owner", + tag("itunes:email", "john.doe@example.com")) + itunes_rss20_parse(content, &rss20_maker) + end + + assert_missing_tag("email", "owner") do + content = tag("itunes:owner", tag("itunes:name", "John Doe")) + itunes_rss20_parse(content, &rss20_maker) + end + end + end + + def _assert_itunes_subtitle(value, readers, &rss20_maker) + content = tag("itunes:subtitle", value) + rss20 = itunes_rss20_parse(content, &rss20_maker) + target = chain_reader(rss20, readers) + assert_equal(value, target.itunes_subtitle) + end + + def assert_itunes_subtitle(readers, &rss20_maker) + _wrap_assertion do + _assert_itunes_subtitle("A show about everything", readers, &rss20_maker) + _assert_itunes_subtitle("A short primer on table spices", + readers, &rss20_maker) + _assert_itunes_subtitle("Comparing socket wrenches is fun!", + readers, &rss20_maker) + _assert_itunes_subtitle("Red + Blue != Purple", readers, &rss20_maker) + end + end + + def _assert_itunes_summary(value, readers, &rss20_maker) + content = tag("itunes:summary", value) + rss20 = itunes_rss20_parse(content, &rss20_maker) + target = chain_reader(rss20, readers) + assert_equal(value, target.itunes_summary) + end + + def assert_itunes_summary(readers, &rss20_maker) + _wrap_assertion do + _assert_itunes_summary("All About Everything is a show about " + + "everything. Each week we dive into any " + + "subject known to man and talk about it as " + + "much as we can. Look for our Podcast in " + + "the iTunes Music Store", + readers, &rss20_maker) + _assert_itunes_summary("This week we talk about salt and pepper " + + "shakers, comparing and contrasting pour " + + "rates, construction materials, and overall " + + "aesthetics. Come and join the party!", + readers, &rss20_maker) + _assert_itunes_summary("This week we talk about metric vs. old " + + "english socket wrenches. Which one is " + + "better? Do you really need both? Get all " + + "of your answers here.", + readers, &rss20_maker) + _assert_itunes_summary("This week we talk about surviving in a " + + "Red state if you’re a Blue person. Or " + + "vice versa.", + readers, &rss20_maker) + end + end + end +end diff --git a/test/rss/test_maker_0.9.rb b/test/rss/test_maker_0.9.rb index 820d567289..df92945a86 100644 --- a/test/rss/test_maker_0.9.rb +++ b/test/rss/test_maker_0.9.rb @@ -11,18 +11,21 @@ module RSS rss = RSS::Maker.make("0.9") do |maker| setup_dummy_channel(maker) + setup_dummy_image(maker) end assert_equal("0.91", rss.rss_version) rss = RSS::Maker.make("0.91") do |maker| setup_dummy_channel(maker) + setup_dummy_image(maker) end assert_equal("0.91", rss.rss_version) rss = RSS::Maker.make("0.91") do |maker| setup_dummy_channel(maker) - + setup_dummy_image(maker) + maker.encoding = "EUC-JP" end assert_equal("0.91", rss.rss_version) @@ -30,7 +33,8 @@ module RSS rss = RSS::Maker.make("0.91") do |maker| setup_dummy_channel(maker) - + setup_dummy_image(maker) + maker.standalone = "yes" end assert_equal("0.91", rss.rss_version) @@ -38,7 +42,8 @@ module RSS rss = RSS::Maker.make("0.91") do |maker| setup_dummy_channel(maker) - + setup_dummy_image(maker) + maker.encoding = "EUC-JP" maker.standalone = "yes" end @@ -67,7 +72,10 @@ module RSS ] pubDate = Time.now lastBuildDate = Time.now - + + image_url = "http://example.com/logo.png" + image_title = "Logo" + rss = RSS::Maker.make("0.91") do |maker| maker.channel.title = title maker.channel.link = link @@ -91,6 +99,9 @@ module RSS new_hour.content = hour end end + + maker.image.url = image_url + maker.image.title = image_title end channel = rss.channel @@ -115,7 +126,11 @@ module RSS end assert(channel.items.empty?) - assert_nil(channel.image) + + assert_equal(image_url, channel.image.url) + assert_equal(image_title, channel.image.title) + assert_equal(link, channel.image.link) + assert_nil(channel.textInput) end @@ -211,17 +226,18 @@ module RSS height = "400" description = "an image" - rss = RSS::Maker.make("0.91") do |maker| - setup_dummy_channel(maker) - maker.channel.link = link - - # maker.image.title = title - maker.image.url = url - maker.image.width = width - maker.image.height = height - maker.image.description = description + assert_not_set_error("maker.image", %w(title)) do + RSS::Maker.make("0.91") do |maker| + setup_dummy_channel(maker) + maker.channel.link = link + + # maker.image.title = title + maker.image.url = url + maker.image.width = width + maker.image.height = height + maker.image.description = description + end end - assert_nil(rss.channel.image) assert_not_set_error("maker.channel", %w(link)) do RSS::Maker.make("0.91") do |maker| @@ -237,26 +253,28 @@ module RSS end end - rss = RSS::Maker.make("0.91") do |maker| - setup_dummy_channel(maker) - maker.channel.link = link - - maker.image.title = title - # maker.image.url = url - maker.image.width = width - maker.image.height = height - maker.image.description = description + assert_not_set_error("maker.image", %w(url)) do + RSS::Maker.make("0.91") do |maker| + setup_dummy_channel(maker) + maker.channel.link = link + + maker.image.title = title + # maker.image.url = url + maker.image.width = width + maker.image.height = height + maker.image.description = description + end end - assert_nil(rss.channel.image) end - def test_items + def test_items(with_convenience_way=true) title = "TITLE" link = "http://hoge.com/" description = "text hoge fuga" rss = RSS::Maker.make("0.91") do |maker| setup_dummy_channel(maker) + setup_dummy_image(maker) end assert(rss.channel.items.empty?) @@ -268,6 +286,8 @@ module RSS item.link = link # item.description = description end + + setup_dummy_image(maker) end assert_equal(1, rss.channel.items.size) item = rss.channel.items.first @@ -281,43 +301,55 @@ module RSS setup_dummy_channel(maker) item_size.times do |i| - maker.items.new_item do |item| - item.title = "#{title}#{i}" - item.link = "#{link}#{i}" - item.description = "#{description}#{i}" + maker.items.new_item do |_item| + _item.title = "#{title}#{i}" + _item.link = "#{link}#{i}" + _item.description = "#{description}#{i}" end end maker.items.do_sort = true + + setup_dummy_image(maker) end assert_equal(item_size, rss.items.size) - rss.channel.items.each_with_index do |item, i| - assert_equal("#{title}#{i}", item.title) - assert_equal("#{link}#{i}", item.link) - assert_equal("#{description}#{i}", item.description) + rss.channel.items.each_with_index do |_item, i| + assert_equal("#{title}#{i}", _item.title) + assert_equal("#{link}#{i}", _item.link) + assert_equal("#{description}#{i}", _item.description) end rss = RSS::Maker.make("0.91") do |maker| setup_dummy_channel(maker) item_size.times do |i| - maker.items.new_item do |item| - item.title = "#{title}#{i}" - item.link = "#{link}#{i}" - item.description = "#{description}#{i}" + maker.items.new_item do |_item| + _item.title = "#{title}#{i}" + _item.link = "#{link}#{i}" + _item.description = "#{description}#{i}" end end maker.items.do_sort = Proc.new do |x, y| - y.title[-1] <=> x.title[-1] + if with_convenience_way + y.title[-1] <=> x.title[-1] + else + y.title {|t| t.content[-1]} <=> x.title {|t| t.content[-1]} + end end + + setup_dummy_image(maker) end assert_equal(item_size, rss.items.size) - rss.channel.items.reverse.each_with_index do |item, i| - assert_equal("#{title}#{i}", item.title) - assert_equal("#{link}#{i}", item.link) - assert_equal("#{description}#{i}", item.description) + rss.channel.items.reverse.each_with_index do |_item, i| + assert_equal("#{title}#{i}", _item.title) + assert_equal("#{link}#{i}", _item.link) + assert_equal("#{description}#{i}", _item.description) end end + def test_items_with_new_api_since_018 + test_items(false) + end + def test_textInput title = "fugafuga" description = "text hoge fuga" @@ -326,6 +358,7 @@ module RSS rss = RSS::Maker.make("0.91") do |maker| setup_dummy_channel(maker) + setup_dummy_image(maker) maker.textinput.title = title maker.textinput.description = description @@ -338,15 +371,17 @@ module RSS assert_equal(name, textInput.name) assert_equal(link, textInput.link) - rss = RSS::Maker.make("0.91") do |maker| - # setup_dummy_channel(maker) + assert_not_set_error("maker.channel", + %w(link language description title)) do + RSS::Maker.make("0.91") do |maker| + # setup_dummy_channel(maker) - maker.textinput.title = title - maker.textinput.description = description - maker.textinput.name = name - maker.textinput.link = link + maker.textinput.title = title + maker.textinput.description = description + maker.textinput.name = name + maker.textinput.link = link + end end - assert_nil(rss) end def test_not_valid_textInput @@ -357,6 +392,7 @@ module RSS rss = RSS::Maker.make("0.91") do |maker| setup_dummy_channel(maker) + setup_dummy_image(maker) # maker.textinput.title = title maker.textinput.description = description @@ -367,7 +403,8 @@ module RSS rss = RSS::Maker.make("0.91") do |maker| setup_dummy_channel(maker) - + setup_dummy_image(maker) + maker.textinput.title = title # maker.textinput.description = description maker.textinput.name = name @@ -377,7 +414,8 @@ module RSS rss = RSS::Maker.make("0.91") do |maker| setup_dummy_channel(maker) - + setup_dummy_image(maker) + maker.textinput.title = title maker.textinput.description = description # maker.textinput.name = name @@ -387,7 +425,8 @@ module RSS rss = RSS::Maker.make("0.91") do |maker| setup_dummy_channel(maker) - + setup_dummy_image(maker) + maker.textinput.title = title maker.textinput.description = description maker.textinput.name = name diff --git a/test/rss/test_maker_1.0.rb b/test/rss/test_maker_1.0.rb index e15432146d..60cc3708a7 100644 --- a/test/rss/test_maker_1.0.rb +++ b/test/rss/test_maker_1.0.rb @@ -8,12 +8,15 @@ module RSS def test_rdf rss = RSS::Maker.make("1.0") do |maker| setup_dummy_channel(maker) + setup_dummy_item(maker) end assert_equal("1.0", rss.rss_version) rss = RSS::Maker.make("1.0") do |maker| setup_dummy_channel(maker) maker.encoding = "EUC-JP" + + setup_dummy_item(maker) end assert_equal("1.0", rss.rss_version) assert_equal("EUC-JP", rss.encoding) @@ -21,6 +24,8 @@ module RSS rss = RSS::Maker.make("1.0") do |maker| setup_dummy_channel(maker) maker.standalone = "yes" + + setup_dummy_item(maker) end assert_equal("1.0", rss.rss_version) assert_equal("yes", rss.standalone) @@ -29,6 +34,8 @@ module RSS setup_dummy_channel(maker) maker.encoding = "EUC-JP" maker.standalone = "yes" + + setup_dummy_item(maker) end assert_equal("1.0", rss.rss_version) assert_equal("EUC-JP", rss.encoding) @@ -49,13 +56,15 @@ module RSS maker.channel.title = title maker.channel.link = link maker.channel.description = description + + setup_dummy_item(maker) end channel = rss.channel assert_equal(about, channel.about) assert_equal(title, channel.title) assert_equal(link, channel.link) assert_equal(description, channel.description) - assert(channel.items.Seq.lis.empty?) + assert_equal(1, channel.items.Seq.lis.size) assert_nil(channel.image) assert_nil(channel.textinput) @@ -68,13 +77,15 @@ module RSS setup_dummy_image(maker) setup_dummy_textinput(maker) + + setup_dummy_item(maker) end channel = rss.channel assert_equal(about, channel.about) assert_equal(title, channel.title) assert_equal(link, channel.link) assert_equal(description, channel.description) - assert(channel.items.Seq.lis.empty?) + assert_equal(1, channel.items.Seq.lis.size) assert_equal(rss.image.about, channel.image.resource) assert_equal(rss.textinput.about, channel.textinput.resource) end @@ -134,6 +145,8 @@ module RSS maker.image.title = title maker.image.url = url + + setup_dummy_item(maker) end image = rss.image assert_equal(url, image.about) @@ -164,6 +177,8 @@ module RSS # maker.image.url = url maker.image.title = title + + setup_dummy_item(maker) end assert_nil(rss.channel.image) assert_nil(rss.image) @@ -174,6 +189,8 @@ module RSS maker.image.url = url # maker.image.title = title + + setup_dummy_item(maker) end assert_nil(rss.channel.image) assert_nil(rss.image) @@ -186,19 +203,22 @@ module RSS maker.image.url = url maker.image.title = title + + setup_dummy_item(maker) end end end - - def test_items + + def test_items(with_convenience_way=true) title = "TITLE" link = "http://hoge.com/" description = "text hoge fuga" - rss = RSS::Maker.make("1.0") do |maker| - setup_dummy_channel(maker) + assert_not_set_error("maker", %w(items)) do + RSS::Maker.make("1.0") do |maker| + setup_dummy_channel(maker) + end end - assert(rss.items.empty?) rss = RSS::Maker.make("1.0") do |maker| setup_dummy_channel(maker) @@ -222,42 +242,46 @@ module RSS setup_dummy_channel(maker) item_size.times do |i| - maker.items.new_item do |item| - item.title = "#{title}#{i}" - item.link = "#{link}#{i}" - item.description = "#{description}#{i}" + maker.items.new_item do |_item| + _item.title = "#{title}#{i}" + _item.link = "#{link}#{i}" + _item.description = "#{description}#{i}" end end maker.items.do_sort = true end assert_equal(item_size, rss.items.size) - rss.items.each_with_index do |item, i| - assert_equal("#{link}#{i}", item.about) - assert_equal("#{title}#{i}", item.title) - assert_equal("#{link}#{i}", item.link) - assert_equal("#{description}#{i}", item.description) + rss.items.each_with_index do |_item, i| + assert_equal("#{link}#{i}", _item.about) + assert_equal("#{title}#{i}", _item.title) + assert_equal("#{link}#{i}", _item.link) + assert_equal("#{description}#{i}", _item.description) end rss = RSS::Maker.make("1.0") do |maker| setup_dummy_channel(maker) item_size.times do |i| - maker.items.new_item do |item| - item.title = "#{title}#{i}" - item.link = "#{link}#{i}" - item.description = "#{description}#{i}" + maker.items.new_item do |_item| + _item.title = "#{title}#{i}" + _item.link = "#{link}#{i}" + _item.description = "#{description}#{i}" end end maker.items.do_sort = Proc.new do |x, y| - y.title[-1] <=> x.title[-1] + if with_convenience_way + y.title[-1] <=> x.title[-1] + else + y.title {|t| t.content[-1]} <=> x.title {|t| t.content[-1]} + end end end assert_equal(item_size, rss.items.size) - rss.items.reverse.each_with_index do |item, i| - assert_equal("#{link}#{i}", item.about) - assert_equal("#{title}#{i}", item.title) - assert_equal("#{link}#{i}", item.link) - assert_equal("#{description}#{i}", item.description) + rss.items.reverse.each_with_index do |_item, i| + assert_equal("#{link}#{i}", _item.about) + assert_equal("#{title}#{i}", _item.title) + assert_equal("#{link}#{i}", _item.link) + assert_equal("#{description}#{i}", _item.description) end max_size = item_size / 2 @@ -265,84 +289,102 @@ module RSS setup_dummy_channel(maker) item_size.times do |i| - maker.items.new_item do |item| - item.title = "#{title}#{i}" - item.link = "#{link}#{i}" - item.description = "#{description}#{i}" + maker.items.new_item do |_item| + _item.title = "#{title}#{i}" + _item.link = "#{link}#{i}" + _item.description = "#{description}#{i}" end end maker.items.max_size = max_size end assert_equal(max_size, rss.items.size) - rss.items.each_with_index do |item, i| - assert_equal("#{link}#{i}", item.about) - assert_equal("#{title}#{i}", item.title) - assert_equal("#{link}#{i}", item.link) - assert_equal("#{description}#{i}", item.description) + rss.items.each_with_index do |_item, i| + assert_equal("#{link}#{i}", _item.about) + assert_equal("#{title}#{i}", _item.title) + assert_equal("#{link}#{i}", _item.link) + assert_equal("#{description}#{i}", _item.description) end max_size = 0 - rss = RSS::Maker.make("1.0") do |maker| - setup_dummy_channel(maker) - - item_size.times do |i| - maker.items.new_item do |item| - item.title = "#{title}#{i}" - item.link = "#{link}#{i}" - item.description = "#{description}#{i}" + assert_not_set_error("maker", %w(items)) do + RSS::Maker.make("1.0") do |maker| + setup_dummy_channel(maker) + + item_size.times do |i| + maker.items.new_item do |_item| + _item.title = "#{title}#{i}" + _item.link = "#{link}#{i}" + _item.description = "#{description}#{i}" + end end + maker.items.max_size = max_size end - maker.items.max_size = max_size end - assert_equal(max_size, rss.items.size) max_size = -2 rss = RSS::Maker.make("1.0") do |maker| setup_dummy_channel(maker) item_size.times do |i| - maker.items.new_item do |item| - item.title = "#{title}#{i}" - item.link = "#{link}#{i}" - item.description = "#{description}#{i}" + maker.items.new_item do |_item| + _item.title = "#{title}#{i}" + _item.link = "#{link}#{i}" + _item.description = "#{description}#{i}" end end maker.items.max_size = max_size end assert_equal(item_size + max_size + 1, rss.items.size) - rss.items.each_with_index do |item, i| - assert_equal("#{link}#{i}", item.about) - assert_equal("#{title}#{i}", item.title) - assert_equal("#{link}#{i}", item.link) - assert_equal("#{description}#{i}", item.description) + rss.items.each_with_index do |_item, i| + assert_equal("#{link}#{i}", _item.about) + assert_equal("#{title}#{i}", _item.title) + assert_equal("#{link}#{i}", _item.link) + assert_equal("#{description}#{i}", _item.description) end end + def test_items_with_new_api_since_018 + test_items(false) + end + def test_not_valid_items title = "TITLE" link = "http://hoge.com/" - rss = RSS::Maker.make("1.0") do |maker| - setup_dummy_channel(maker) - - maker.items.new_item do |item| - # item.title = title - item.link = link + assert_not_set_error("maker.item", %w(title)) do + RSS::Maker.make("1.0") do |maker| + setup_dummy_channel(maker) + + maker.items.new_item do |item| + # item.title = title + item.link = link + end end end - assert(rss.items.empty?) - rss = RSS::Maker.make("1.0") do |maker| - setup_dummy_channel(maker) - - maker.items.new_item do |item| - item.title = title - # item.link = link + assert_not_set_error("maker.item", %w(link)) do + RSS::Maker.make("1.0") do |maker| + setup_dummy_channel(maker) + + maker.items.new_item do |item| + item.title = title + # item.link = link + end + end + end + + assert_not_set_error("maker.item", %w(title link)) do + RSS::Maker.make("1.0") do |maker| + setup_dummy_channel(maker) + + maker.items.new_item do |item| + # item.title = title + # item.link = link + end end end - assert(rss.items.empty?) end - + def test_textinput title = "fugafuga" description = "text hoge fuga" @@ -356,6 +398,8 @@ module RSS maker.textinput.title = title maker.textinput.description = description maker.textinput.name = name + + setup_dummy_item(maker) end textinput = rss.textinput assert_equal(link, textinput.about) @@ -365,15 +409,16 @@ module RSS assert_equal(description, textinput.description) assert_equal(link, textinput.link) - rss = RSS::Maker.make("1.0") do |maker| - # setup_dummy_channel(maker) + assert_not_set_error("maker.channel", %w(about link description title)) do + RSS::Maker.make("1.0") do |maker| + # setup_dummy_channel(maker) - maker.textinput.link = link - maker.textinput.title = title - maker.textinput.description = description - maker.textinput.name = name + maker.textinput.link = link + maker.textinput.title = title + maker.textinput.description = description + maker.textinput.name = name + end end - assert_nil(rss) end def test_not_valid_textinput @@ -389,6 +434,8 @@ module RSS maker.textinput.title = title maker.textinput.description = description maker.textinput.name = name + + setup_dummy_item(maker) end assert_nil(rss.channel.textinput) assert_nil(rss.textinput) @@ -400,6 +447,8 @@ module RSS # maker.textinput.title = title maker.textinput.description = description maker.textinput.name = name + + setup_dummy_item(maker) end assert_nil(rss.channel.textinput) assert_nil(rss.textinput) @@ -411,6 +460,8 @@ module RSS maker.textinput.title = title # maker.textinput.description = description maker.textinput.name = name + + setup_dummy_item(maker) end assert_nil(rss.channel.textinput) assert_nil(rss.textinput) @@ -422,6 +473,8 @@ module RSS maker.textinput.title = title maker.textinput.description = description # maker.textinput.name = name + + setup_dummy_item(maker) end assert_nil(rss.channel.textinput) assert_nil(rss.textinput) diff --git a/test/rss/test_maker_2.0.rb b/test/rss/test_maker_2.0.rb index ec5f3abb8e..8fd9134f70 100644 --- a/test/rss/test_maker_2.0.rb +++ b/test/rss/test_maker_2.0.rb @@ -353,7 +353,7 @@ module RSS assert_nil(rss.image) end - def test_items + def test_items(with_convenience_way=true) title = "TITLE" link = "http://hoge.com/" description = "text hoge fuga" @@ -407,7 +407,11 @@ module RSS end end maker.items.do_sort = Proc.new do |x, y| - y.title[-1] <=> x.title[-1] + if with_convenience_way + y.title[-1] <=> x.title[-1] + else + y.title {|t| t.content[-1]} <=> x.title {|t| t.content[-1]} + end end end assert_equal(item_size, rss.items.size) @@ -422,6 +426,10 @@ module RSS end end + def test_items_with_new_api_since_018 + test_items(false) + end + def test_guid isPermaLink = "true" content = "http://inessential.com/2002/09/01.php#a2" @@ -606,15 +614,16 @@ module RSS assert_equal(name, textInput.name) assert_equal(link, textInput.link) - rss = RSS::Maker.make("2.0") do |maker| - # setup_dummy_channel(maker) + assert_not_set_error("maker.channel", %w(link description title)) do + RSS::Maker.make("2.0") do |maker| + # setup_dummy_channel(maker) - maker.textinput.title = title - maker.textinput.description = description - maker.textinput.name = name - maker.textinput.link = link + maker.textinput.title = title + maker.textinput.description = description + maker.textinput.name = name + maker.textinput.link = link + end end - assert_nil(rss) end def test_not_valid_textInput diff --git a/test/rss/test_maker_atom_entry.rb b/test/rss/test_maker_atom_entry.rb new file mode 100644 index 0000000000..ae0ab2d09f --- /dev/null +++ b/test/rss/test_maker_atom_entry.rb @@ -0,0 +1,367 @@ +require "rss-testcase" + +require "rss/maker" + +module RSS + class TestMakerAtomEntry < TestCase + def test_root_element + entry = Maker.make("atom:entry") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + assert_equal(["atom", "1.0", "entry"], entry.feed_info) + + entry = Maker.make("atom:entry") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + maker.encoding = "EUC-JP" + end + assert_equal(["atom", "1.0", "entry"], entry.feed_info) + assert_equal("EUC-JP", entry.encoding) + + entry = Maker.make("atom:entry") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + maker.standalone = "yes" + end + assert_equal(["atom", "1.0", "entry"], entry.feed_info) + assert_equal("yes", entry.standalone) + + entry = Maker.make("atom:entry") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + maker.encoding = "EUC-JP" + maker.standalone = "yes" + end + assert_equal(["atom", "1.0", "entry"], entry.feed_info) + assert_equal("EUC-JP", entry.encoding) + assert_equal("yes", entry.standalone) + end + + def test_invalid_feed + assert_not_set_error("maker.item", %w(id title author updated)) do + Maker.make("atom:entry") do |maker| + end + end + + assert_not_set_error("maker.item", %w(id title updated)) do + Maker.make("atom:entry") do |maker| + maker.channel.author = "foo" + end + end + + assert_not_set_error("maker.item", %w(title updated)) do + Maker.make("atom:entry") do |maker| + maker.channel.author = "foo" + maker.channel.id = "http://example.com" + end + end + + assert_not_set_error("maker.item", %w(updated)) do + Maker.make("atom:entry") do |maker| + maker.channel.author = "foo" + maker.channel.id = "http://example.com" + maker.channel.title = "Atom Feed" + end + end + + assert_not_set_error("maker.item", %w(author)) do + Maker.make("atom:entry") do |maker| + maker.channel.id = "http://example.com" + maker.channel.title = "Atom Feed" + maker.channel.updated = Time.now + end + end + + entry = Maker.make("atom:entry") do |maker| + maker.channel.author = "Foo" + maker.channel.id = "http://example.com" + maker.channel.title = "Atom Feed" + maker.channel.updated = Time.now + end + assert_not_nil(entry) + end + + def test_author + assert_maker_atom_persons("entry", + ["channel", "authors"], + ["authors"], + "maker.channel.author") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + + assert_maker_atom_persons("entry", + ["items", "first", "authors"], + ["authors"], + "maker.item.author", + "maker.item", ["author"]) do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + maker.channel.authors.clear + maker.items.first.authors.clear + end + + assert_maker_atom_persons("entry", + ["items", "first", "source", "authors"], + ["source", "authors"], + "maker.item.source.author") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_category + assert_maker_atom_categories("entry", + ["channel", "categories"], + ["categories"], + "maker.channel.category") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + + assert_maker_atom_categories("entry", + ["items", "first", "categories"], + ["categories"], + "maker.item.category") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + + assert_maker_atom_categories("entry", + ["items", "first", "source", "categories"], + ["source", "categories"], + "maker.item.source.category") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_content + assert_maker_atom_content("entry", + ["items", "first", "content"], + ["content"], + "maker.item.content") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_contributor + assert_maker_atom_persons("entry", + ["channel", "contributors"], + ["contributors"], + "maker.channel.contributor") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + + assert_maker_atom_persons("entry", + ["items", "first", "contributors"], + ["contributors"], + "maker.item.contributor") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + + assert_maker_atom_persons("entry", + ["items", "first", "source", "contributors"], + ["source", "contributors"], + "maker.item.source.contributor") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_link + assert_maker_atom_links("entry", + ["channel", "links"], + ["links"], + "maker.channel.link") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + maker.channel.links.clear + maker.items.first.links.clear + end + + assert_maker_atom_links("entry", + ["items", "first", "links"], + ["links"], + "maker.item.link") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + maker.channel.links.clear + maker.items.first.links.clear + end + + assert_maker_atom_links("entry", + ["items", "first", "source", "links"], + ["source", "links"], + "maker.item.source.link", true) do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_published + assert_maker_atom_date_construct("entry", + ["items", "first", "published"], + ["published"] + ) do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_rights + assert_maker_atom_text_construct("entry", + ["channel", "copyright"], + ["rights"]) do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + + assert_maker_atom_text_construct("entry", + ["items", "first", "rights"], + ["rights"], + nil, nil, "maker.item.rights" + ) do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + + assert_maker_atom_text_construct("entry", + ["items", "first", "source", "rights"], + ["source", "rights"], + nil, nil, "maker.item.source.rights" + ) do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + + def test_source_generator + assert_maker_atom_generator("entry", + ["items", "first", "source", "generator"], + ["source", "generator"], + "maker.item.source.generator") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_source_icon + assert_maker_atom_icon("entry", + ["items", "first", "source", "icon"], + ["source", "icon"], + nil, "maker.item.source.icon") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_source_id + assert_maker_atom_id("entry", + ["items", "first", "source"], + ["source"], + "maker.item.source") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_source_logo + assert_maker_atom_logo("entry", + ["items", "first", "source", "logo"], + ["source", "logo"], + nil, + "maker.item.source.logo") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_source_subtitle + assert_maker_atom_text_construct("entry", + ["items", "first", "source", "subtitle"], + ["source", "subtitle"], + nil, nil, + "maker.item.source.subtitle") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_summary + assert_maker_atom_text_construct("entry", + ["items", "first", "description"], + ["summary"], + nil, nil, "maker.item.description" + ) do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_title + assert_maker_atom_text_construct("entry", + ["channel", "title"], ["title"], + "maker.item", ["title"], + "maker.channel.title") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + maker.channel.title = nil + maker.items.first.title = nil + end + + assert_maker_atom_text_construct("entry", + ["items", "first", "title"], + ["title"], + "maker.item", ["title"], + "maker.item.title") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + maker.channel.title = nil + maker.items.first.title = nil + end + + assert_maker_atom_text_construct("entry", + ["items", "first", "source", "title"], + ["source", "title"], + nil, nil, "maker.item.source.title" + ) do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_updated + assert_maker_atom_date_construct("entry", + ["channel", "updated"], ["updated"], + "maker.item", ["updated"]) do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + maker.channel.updated = nil + maker.items.first.updated = nil + end + + assert_maker_atom_date_construct("entry", + ["items", "first", "updated"], + ["updated"], + "maker.item", ["updated"]) do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + maker.channel.updated = nil + maker.items.first.updated = nil + end + + assert_maker_atom_date_construct("entry", + ["items", "first", "source", "updated"], + ["source", "updated"]) do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + end +end diff --git a/test/rss/test_maker_atom_feed.rb b/test/rss/test_maker_atom_feed.rb new file mode 100644 index 0000000000..e5bf0e23b2 --- /dev/null +++ b/test/rss/test_maker_atom_feed.rb @@ -0,0 +1,389 @@ +require "rss-testcase" + +require "rss/maker" + +module RSS + class TestMakerAtomFeed < TestCase + def test_root_element + feed = Maker.make("atom") do |maker| + setup_dummy_channel_atom(maker) + end + assert_equal(["atom", "1.0", "feed"], feed.feed_info) + + feed = Maker.make("atom") do |maker| + setup_dummy_channel_atom(maker) + maker.encoding = "EUC-JP" + end + assert_equal(["atom", "1.0", "feed"], feed.feed_info) + assert_equal("EUC-JP", feed.encoding) + + feed = Maker.make("atom") do |maker| + setup_dummy_channel_atom(maker) + maker.standalone = "yes" + end + assert_equal(["atom", "1.0", "feed"], feed.feed_info) + assert_equal("yes", feed.standalone) + + feed = Maker.make("atom") do |maker| + setup_dummy_channel_atom(maker) + maker.encoding = "EUC-JP" + maker.standalone = "yes" + end + assert_equal(["atom", "1.0", "feed"], feed.feed_info) + assert_equal("EUC-JP", feed.encoding) + assert_equal("yes", feed.standalone) + end + + def test_invalid_feed + assert_not_set_error("maker.channel", %w(id title author updated)) do + Maker.make("atom") do |maker| + end + end + + assert_not_set_error("maker.channel", %w(id title updated)) do + Maker.make("atom") do |maker| + maker.channel.author = "foo" + end + end + + assert_not_set_error("maker.channel", %w(title updated)) do + Maker.make("atom") do |maker| + maker.channel.author = "foo" + maker.channel.id = "http://example.com" + end + end + + assert_not_set_error("maker.channel", %w(updated)) do + Maker.make("atom") do |maker| + maker.channel.author = "foo" + maker.channel.id = "http://example.com" + maker.channel.title = "Atom Feed" + end + end + + assert_not_set_error("maker.channel", %w(author)) do + Maker.make("atom") do |maker| + maker.channel.id = "http://example.com" + maker.channel.title = "Atom Feed" + maker.channel.updated = Time.now + end + end + + feed = Maker.make("atom") do |maker| + maker.channel.author = "Foo" + maker.channel.id = "http://example.com" + maker.channel.title = "Atom Feed" + maker.channel.updated = Time.now + end + assert_not_nil(feed) + end + + def test_author + assert_maker_atom_persons("feed", + ["channel", "authors"], + ["authors"], + "maker.channel.author") do |maker| + setup_dummy_channel_atom(maker) + end + + assert_not_set_error("maker.channel", %w(author)) do + RSS::Maker.make("atom") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + maker.channel.authors.clear + end + end + + assert_maker_atom_persons("feed", + ["items", "first", "authors"], + ["entries", "first", "authors"], + "maker.item.author") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + + assert_maker_atom_persons("feed", + ["items", "first", "source", "authors"], + ["entries", "first", "source", "authors"], + "maker.item.source.author") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_category + assert_maker_atom_categories("feed", + ["channel", "categories"], + ["categories"], + "maker.channel.category") do |maker| + setup_dummy_channel_atom(maker) + end + + assert_maker_atom_categories("feed", + ["items", "first", "categories"], + ["entries", "first", "categories"], + "maker.item.category") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + + assert_maker_atom_categories("feed", + ["items", "first", "source", "categories"], + ["entries", "first", "source", "categories"], + "maker.item.source.category") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_contributor + assert_maker_atom_persons("feed", + ["channel", "contributors"], + ["contributors"], + "maker.channel.contributor") do |maker| + setup_dummy_channel_atom(maker) + end + + assert_maker_atom_persons("feed", + ["items", "first", "contributors"], + ["entries", "first", "contributors"], + "maker.item.contributor") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + + assert_maker_atom_persons("feed", + ["items", "first", "source", "contributors"], + ["entries", "first", "source", "contributors"], + "maker.item.source.contributor") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_generator + assert_maker_atom_generator("feed", + ["channel", "generator"], + ["generator"]) do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + + assert_maker_atom_generator("feed", + ["items", "first", "source", "generator"], + ["entries", "first", "source", "generator"], + "maker.item.source.generator") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_icon + assert_maker_atom_icon("feed", ["channel"], ["icon"], "icon") do |maker| + setup_dummy_channel_atom(maker) + end + + assert_maker_atom_icon("feed", + ["items", "first", "source", "icon"], + ["entries", "first", "source", "icon"], + nil, "maker.item.source.icon") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_link + assert_maker_atom_links("feed", + ["channel", "links"], + ["links"], + "maker.channel.link") do |maker| + setup_dummy_channel_atom(maker) + end + + assert_maker_atom_links("feed", + ["items", "first", "links"], + ["entries", "first", "links"], + "maker.item.link") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + + assert_maker_atom_links("feed", + ["items", "first", "source", "links"], + ["entries", "first", "source", "links"], + "maker.item.source.link", true) do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_logo + assert_maker_atom_logo("feed", ["channel"], ["logo"], "logo") do |maker| + setup_dummy_channel_atom(maker) + end + + assert_maker_atom_logo("feed", ["image"], ["logo"], "url") do |maker| + setup_dummy_channel_atom(maker) + end + + assert_maker_atom_logo("feed", + ["items", "first", "source", "logo"], + ["entries", "first", "source", "logo"], + nil, "maker.item.source.logo") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_rights + assert_maker_atom_text_construct("feed", + ["channel", "copyright"], + ["rights"]) do |maker| + setup_dummy_channel_atom(maker) + end + + assert_maker_atom_text_construct("feed", + ["items", "first", "rights"], + ["entries", "first", "rights"], + nil, nil, "maker.item.rights" + ) do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + + assert_maker_atom_text_construct("feed", + ["items", "first", "source", "rights"], + ["entries", "first", "source", "rights"], + nil, nil, "maker.item.source.rights" + ) do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_subtitle + assert_maker_atom_text_construct("feed", + ["channel", "subtitle"], + ["subtitle"], + nil, nil, + "maker.channel.description") do |maker| + setup_dummy_channel_atom(maker) + maker.channel.description = nil + end + + assert_maker_atom_text_construct("feed", + ["channel", "subtitle"], + ["subtitle"], + nil, nil, + "maker.channel.description") do |maker| + setup_dummy_channel_atom(maker) + maker.channel.description {|d| d.content = nil} + end + + assert_maker_atom_text_construct("feed", + ["items", "first", "source", "subtitle"], + ["entries", "first", + "source", "subtitle"], + nil, nil, + "maker.item.source.subtitle") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_title + assert_maker_atom_text_construct("feed", + ["channel", "title"], ["title"], + "maker.channel", ["title"]) do |maker| + setup_dummy_channel_atom(maker) + maker.channel.title = nil + end + + assert_maker_atom_text_construct("feed", + ["items", "first", "title"], + ["entries", "first", "title"], + "maker.item", ["title"], + "maker.item.title") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + maker.items.first.title = nil + end + + assert_maker_atom_text_construct("feed", + ["items", "first", "source", "title"], + ["entries", "first", "source", "title"], + nil, nil, "maker.item.source.title" + ) do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_updated + assert_maker_atom_date_construct("feed", + ["channel", "updated"], ["updated"], + "maker.channel", ["updated"]) do |maker| + setup_dummy_channel_atom(maker) + maker.channel.updated = nil + end + + assert_maker_atom_date_construct("feed", + ["items", "first", "updated"], + ["entries", "first", "updated"], + "maker.item", ["updated"]) do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + maker.items.first.updated = nil + end + + assert_maker_atom_date_construct("feed", + ["items", "first", "source", "updated"], + ["entries", "first", "source", "updated"] + ) do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_published + assert_maker_atom_date_construct("feed", + ["items", "first", "published"], + ["entries", "first", "published"] + ) do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_summary + assert_maker_atom_text_construct("feed", + ["items", "first", "description"], + ["entries", "first", "summary"], + nil, nil, "maker.item.description" + ) do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_content + assert_maker_atom_content("feed", + ["items", "first", "content"], + ["entries", "first", "content"], + "maker.item.content") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + + def test_id + assert_maker_atom_id("feed", + ["items", "first", "source"], + ["entries", "first", "source"], + "maker.item.source") do |maker| + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + end + end +end diff --git a/test/rss/test_maker_dc.rb b/test/rss/test_maker_dc.rb index 6266e953aa..c9df36a1ce 100644 --- a/test/rss/test_maker_dc.rb +++ b/test/rss/test_maker_dc.rb @@ -57,9 +57,14 @@ module RSS end def test_rss10_multiple + assert_multiple_dublin_core_rss10("_list") + assert_multiple_dublin_core_rss10("es") + end + + def assert_multiple_dublin_core_rss10(multiple_rights_suffix) elems = [] @elements.each do |name, value| - plural = name.to_s + (name == :rights ? "es" : "s") + plural = name.to_s + (name == :rights ? multiple_rights_suffix : "s") values = [value] if name == :date values << value + 60 @@ -68,7 +73,7 @@ module RSS end elems << [name, values, plural] end - + rss = RSS::Maker.make("1.0") do |maker| setup_dummy_channel(maker) set_multiple_elements(maker.channel, elems) diff --git a/test/rss/test_maker_itunes.rb b/test/rss/test_maker_itunes.rb new file mode 100644 index 0000000000..1d4e323057 --- /dev/null +++ b/test/rss/test_maker_itunes.rb @@ -0,0 +1,471 @@ +require "rss-testcase" + +require "rss/maker" + +module RSS + class TestMakerITunes < TestCase + def test_author + assert_maker_itunes_author(%w(channel)) + assert_maker_itunes_author(%w(items last)) + end + + def test_block + assert_maker_itunes_block(%w(channel)) + assert_maker_itunes_block(%w(items last)) + end + + def test_category + assert_maker_itunes_category(%w(channel)) + end + + def test_image + assert_maker_itunes_image(%w(channel)) + end + + def test_duration + assert_maker_itunes_duration(%w(items last)) + end + + def test_explicit + assert_maker_itunes_explicit(%w(channel)) + assert_maker_itunes_explicit(%w(items last)) + end + + def test_keywords + assert_maker_itunes_keywords(%w(channel)) + assert_maker_itunes_keywords(%w(items last)) + end + + def test_new_feed_url + assert_maker_itunes_new_feed_url(%w(channel)) + end + + def test_owner + assert_maker_itunes_owner(%w(channel)) + end + + def test_subtitle + assert_maker_itunes_subtitle(%w(channel)) + assert_maker_itunes_subtitle(%w(items last)) + end + + def test_summary + assert_maker_itunes_summary(%w(channel)) + assert_maker_itunes_summary(%w(items last)) + end + + private + + def assert_maker_itunes_author(maker_readers, feed_readers=nil) + _wrap_assertion do + feed_readers ||= maker_readers + author = "John Doe" + rss20 = ::RSS::Maker.make("rss2.0") do |maker| + setup_dummy_channel(maker) + setup_dummy_item(maker) + + target = chain_reader(maker, maker_readers) + target.itunes_author = author + end + target = chain_reader(rss20, feed_readers) + assert_equal(author, target.itunes_author) + end + end + + def _assert_maker_itunes_block(value, boolean_value, maker_readers, + feed_readers) + rss20 = ::RSS::Maker.make("rss2.0") do |maker| + setup_dummy_channel(maker) + setup_dummy_item(maker) + + target = chain_reader(maker, maker_readers) + target.itunes_block = value + assert_equal(value, target.itunes_block) + assert_equal(boolean_value, target.itunes_block?) + end + target = chain_reader(rss20, feed_readers) + if [true, false].include?(value) + feed_expected_value = value = value ? "yes" : "no" + else + feed_expected_value = value + end + assert_equal(value, target.itunes_block) + assert_equal(boolean_value, target.itunes_block?) + end + + def assert_maker_itunes_block(maker_readers, feed_readers=nil) + _wrap_assertion do + feed_readers ||= maker_readers + _assert_maker_itunes_block("yes", true, maker_readers, feed_readers) + _assert_maker_itunes_block("Yes", true, maker_readers, feed_readers) + _assert_maker_itunes_block("no", false, maker_readers, feed_readers) + _assert_maker_itunes_block("", false, maker_readers, feed_readers) + _assert_maker_itunes_block(true, true, maker_readers, feed_readers) + _assert_maker_itunes_block(false, false, maker_readers, feed_readers) + _assert_maker_itunes_block(nil, false, maker_readers, feed_readers) + end + end + + def _assert_maker_itunes_category(categories, maker_readers, feed_readers) + rss20 = ::RSS::Maker.make("rss2.0") do |maker| + setup_dummy_channel(maker) + setup_dummy_item(maker) + + target = chain_reader(maker, maker_readers) + categories.each do |category| + sub_target = target.itunes_categories + if category.is_a?(Array) + category.each do |sub_category| + sub_target = sub_target.new_category + sub_target.text = sub_category + end + else + sub_target.new_category.text = category + end + end + end + + target = chain_reader(rss20, feed_readers) + actual_categories = target.itunes_categories.collect do |category| + cat = category.text + if category.itunes_categories.empty? + cat + else + [cat, *category.itunes_categories.collect {|c| c.text}] + end + end + assert_equal(categories, actual_categories) + end + + def assert_maker_itunes_category(maker_readers, feed_readers=nil) + _wrap_assertion do + feed_readers ||= maker_readers + _assert_maker_itunes_category(["Audio Blogs"], + maker_readers, feed_readers) + _assert_maker_itunes_category([["Arts & Entertainment", "Games"]], + maker_readers, feed_readers) + _assert_maker_itunes_category([["Arts & Entertainment", "Games"], + ["Technology", "Computers"], + "Audio Blogs"], + maker_readers, feed_readers) + end + end + + def assert_maker_itunes_image(maker_readers, feed_readers=nil) + _wrap_assertion do + feed_readers ||= maker_readers + url = "http://example.com/podcasts/everything/AllAboutEverything.jpg" + + rss20 = ::RSS::Maker.make("rss2.0") do |maker| + setup_dummy_channel(maker) + setup_dummy_item(maker) + + target = chain_reader(maker, maker_readers) + target.itunes_image = url + end + + target = chain_reader(rss20, feed_readers) + assert_not_nil(target.itunes_image) + assert_equal(url, target.itunes_image.href) + end + end + + def _assert_maker_itunes_duration(hour, minute, second, value, + maker_readers, feed_readers) + _assert_maker_itunes_duration_by_value(hour, minute, second, value, + maker_readers, feed_readers) + _assert_maker_itunes_duration_by_hour_minute_second(hour, minute, second, + value, + maker_readers, + feed_readers) + end + + def _assert_maker_itunes_duration_by(hour, minute, second, value, + maker_readers, feed_readers) + expected_value = nil + rss20 = ::RSS::Maker.make("rss2.0") do |maker| + setup_dummy_channel(maker) + setup_dummy_item(maker) + + target = chain_reader(maker, maker_readers) + expected_value = yield(target) + assert_equal(expected_value, target.itunes_duration) + target.itunes_duration do |duration| + assert_equal([hour, minute, second, expected_value], + [duration.hour, duration.minute, + duration.second, duration.content]) + end + end + target = chain_reader(rss20, feed_readers) + duration = target.itunes_duration + assert_not_nil(duration) + assert_equal([hour, minute, second, expected_value], + [duration.hour, duration.minute, + duration.second, duration.content]) + end + + def _assert_maker_itunes_duration_by_value(hour, minute, second, value, + maker_readers, feed_readers) + _assert_maker_itunes_duration_by(hour, minute, second, value, + maker_readers, feed_readers) do |target| + target.itunes_duration = value + value + end + end + + def _assert_maker_itunes_duration_by_hour_minute_second(hour, minute, second, + value, + maker_readers, + feed_readers) + _assert_maker_itunes_duration_by(hour, minute, second, value, + maker_readers, feed_readers) do |target| + target.itunes_duration do |duration| + duration.hour = hour + duration.minute = minute + duration.second = second + end + value.split(":").collect {|v| "%02d" % v.to_i}.join(":") + end + end + + def _assert_maker_itunes_duration_invalid_value(value, maker_readers) + assert_raise(ArgumentError) do + ::RSS::Maker.make("rss2.0") do |maker| + setup_dummy_channel(maker) + setup_dummy_item(maker) + + target = chain_reader(maker, maker_readers) + target.itunes_duration = value + end + end + end + + def assert_maker_itunes_duration(maker_readers, feed_readers=nil) + _wrap_assertion do + feed_readers ||= maker_readers + _assert_maker_itunes_duration(7, 14, 5, "07:14:05", maker_readers, + feed_readers) + _assert_maker_itunes_duration(7, 14, 5, "7:14:05", maker_readers, + feed_readers) + _assert_maker_itunes_duration(0, 4, 55, "04:55", maker_readers, + feed_readers) + _assert_maker_itunes_duration(0, 4, 5, "4:05", maker_readers, + feed_readers) + + _assert_maker_itunes_duration_invalid_value("5", maker_readers) + _assert_maker_itunes_duration_invalid_value("09:07:14:05", maker_readers) + _assert_maker_itunes_duration_invalid_value("10:5", maker_readers) + _assert_maker_itunes_duration_invalid_value("10:03:5", maker_readers) + _assert_maker_itunes_duration_invalid_value("10:3:05", maker_readers) + + _assert_maker_itunes_duration_invalid_value("xx:xx:xx", maker_readers) + end + end + + def _assert_maker_itunes_explicit(explicit, value, + maker_readers, feed_readers) + rss20 = ::RSS::Maker.make("rss2.0") do |maker| + setup_dummy_channel(maker) + setup_dummy_item(maker) + + target = chain_reader(maker, maker_readers) + target.itunes_explicit = value + assert_equal(explicit, target.itunes_explicit?) + end + target = chain_reader(rss20, feed_readers) + assert_equal(value, target.itunes_explicit) + assert_equal(explicit, target.itunes_explicit?) + end + + def assert_maker_itunes_explicit(maker_readers, feed_readers=nil) + _wrap_assertion do + feed_readers ||= maker_readers + _assert_maker_itunes_explicit(true, "yes", maker_readers, feed_readers) + _assert_maker_itunes_explicit(false, "clean", + maker_readers, feed_readers) + _assert_maker_itunes_explicit(nil, "no", maker_readers, feed_readers) + end + end + + def _assert_maker_itunes_keywords(keywords, value, + maker_readers, feed_readers) + _assert_maker_itunes_keywords_by_value(keywords, value, + maker_readers, feed_readers) + _assert_maker_itunes_keywords_by_keywords(keywords, maker_readers, + feed_readers) + end + + def _assert_maker_itunes_keywords_by(keywords, maker_readers, feed_readers) + rss20 = ::RSS::Maker.make("rss2.0") do |maker| + setup_dummy_channel(maker) + setup_dummy_item(maker) + + target = chain_reader(maker, maker_readers) + yield(target) + end + assert_nothing_raised do + rss20 = ::RSS::Parser.parse(rss20.to_s) + end + target = chain_reader(rss20, feed_readers) + assert_equal(keywords, target.itunes_keywords) + end + + def _assert_maker_itunes_keywords_by_value(keywords, value, + maker_readers, feed_readers) + _assert_maker_itunes_keywords_by(keywords, maker_readers, + feed_readers) do |target| + target.itunes_keywords = value + end + end + + def _assert_maker_itunes_keywords_by_keywords(keywords, + maker_readers, feed_readers) + _assert_maker_itunes_keywords_by(keywords, maker_readers, + feed_readers) do |target| + target.itunes_keywords = keywords + end + end + + def assert_maker_itunes_keywords(maker_readers, feed_readers=nil) + _wrap_assertion do + feed_readers ||= maker_readers + _assert_maker_itunes_keywords(["salt"], "salt", + maker_readers, feed_readers) + _assert_maker_itunes_keywords(["salt"], " salt ", + maker_readers, feed_readers) + _assert_maker_itunes_keywords(["salt", "pepper", "shaker", "exciting"], + "salt, pepper, shaker, exciting", + maker_readers, feed_readers) + _assert_maker_itunes_keywords(["metric", "socket", "wrenches", + "toolsalt"], + "metric, socket, wrenches, toolsalt", + maker_readers, feed_readers) + _assert_maker_itunes_keywords(["olitics", "red", "blue", "state"], + "olitics, red, blue, state", + maker_readers, feed_readers) + end + end + + def assert_maker_itunes_new_feed_url(maker_readers, feed_readers=nil) + feed_readers ||= maker_readers + url = "http://newlocation.com/example.rss" + + rss20 = ::RSS::Maker.make("rss2.0") do |maker| + setup_dummy_channel(maker) + setup_dummy_item(maker) + + target = chain_reader(maker, maker_readers) + target.itunes_new_feed_url = url + end + target = chain_reader(rss20, feed_readers) + assert_equal(url, target.itunes_new_feed_url) + end + + def _assert_maker_itunes_owner(name, email, maker_readers, feed_readers) + rss20 = ::RSS::Maker.make("rss2.0") do |maker| + setup_dummy_channel(maker) + setup_dummy_item(maker) + + target = chain_reader(maker, maker_readers) + owner = target.itunes_owner + owner.itunes_name = name + owner.itunes_email = email + end + owner = chain_reader(rss20, feed_readers).itunes_owner + if name.nil? and email.nil? + assert_nil(owner) + else + assert_not_nil(owner) + assert_equal(name, owner.itunes_name) + assert_equal(email, owner.itunes_email) + end + end + + def assert_maker_itunes_owner(maker_readers, feed_readers=nil) + _wrap_assertion do + feed_readers ||= maker_readers + _assert_maker_itunes_owner("John Doe", "john.doe@example.com", + maker_readers, feed_readers) + + not_set_name = (["maker"] + maker_readers + ["itunes_owner"]).join(".") + assert_not_set_error(not_set_name, ["itunes_name"]) do + _assert_maker_itunes_owner(nil, "john.doe@example.com", + maker_readers, feed_readers) + end + assert_not_set_error(not_set_name, ["itunes_email"]) do + _assert_maker_itunes_owner("John Doe", nil, + maker_readers, feed_readers) + end + + _assert_maker_itunes_owner(nil, nil, maker_readers, feed_readers) + end + end + + def _assert_maker_itunes_subtitle(subtitle, maker_readers, feed_readers) + rss20 = ::RSS::Maker.make("rss2.0") do |maker| + setup_dummy_channel(maker) + setup_dummy_item(maker) + + target = chain_reader(maker, maker_readers) + target.itunes_subtitle = subtitle + end + + target = chain_reader(rss20, feed_readers) + assert_equal(subtitle, target.itunes_subtitle) + end + + def assert_maker_itunes_subtitle(maker_readers, feed_readers=nil) + _wrap_assertion do + feed_readers ||= maker_readers + _assert_maker_itunes_subtitle("A show about everything", + maker_readers, feed_readers) + _assert_maker_itunes_subtitle("A short primer on table spices", + maker_readers, feed_readers) + _assert_maker_itunes_subtitle("Comparing socket wrenches is fun!", + maker_readers, feed_readers) + _assert_maker_itunes_subtitle("Red + Blue != Purple", + maker_readers, feed_readers) + end + end + + def _assert_maker_itunes_summary(summary, maker_readers, feed_readers) + rss20 = ::RSS::Maker.make("rss2.0") do |maker| + setup_dummy_channel(maker) + setup_dummy_item(maker) + + target = chain_reader(maker, maker_readers) + target.itunes_summary = summary + end + + target = chain_reader(rss20, feed_readers) + assert_equal(summary, target.itunes_summary) + end + + def assert_maker_itunes_summary(maker_readers, feed_readers=nil) + _wrap_assertion do + feed_readers ||= maker_readers + _assert_maker_itunes_summary("All About Everything is a show about " + + "everything. Each week we dive into any " + + "subject known to man and talk about it " + + "as much as we can. Look for our Podcast " + + "in the iTunes Music Store", + maker_readers, feed_readers) + _assert_maker_itunes_summary("This week we talk about salt and pepper " + + "shakers, comparing and contrasting pour " + + "rates, construction materials, and " + + "overall aesthetics. Come and join the " + + "party!", + maker_readers, feed_readers) + _assert_maker_itunes_summary("This week we talk about metric vs. old " + + "english socket wrenches. Which one is " + + "better? Do you really need both? Get " + + "all of your answers here.", + maker_readers, feed_readers) + _assert_maker_itunes_summary("This week we talk about surviving in a " + + "Red state if you’re a Blue person. Or " + + "vice versa.", + maker_readers, feed_readers) + end + end + end +end diff --git a/test/rss/test_maker_slash.rb b/test/rss/test_maker_slash.rb new file mode 100644 index 0000000000..f2fbf9a231 --- /dev/null +++ b/test/rss/test_maker_slash.rb @@ -0,0 +1,37 @@ +require "rss-testcase" + +require "rss/maker" + +module RSS + class TestMakerSlash < TestCase + def setup + @elements = { + "section" => "articles", + "department" => "not-an-ocean-unless-there-are-lobsters", + "comments" => 177, + "hit_parades" => [177, 155, 105, 33, 6, 3, 0], + } + end + + def test_rss10 + rss = RSS::Maker.make("1.0") do |maker| + setup_dummy_channel(maker) + + setup_dummy_item(maker) + item = maker.items.last + @elements.each do |name, value| + item.send("slash_#{name}=", value) + end + end + + item = rss.items.last + assert_not_nil(item) + assert_slash_elements(item) + end + + private + def assert_slash_elements(target) + super(@elements, target) + end + end +end diff --git a/test/rss/test_maker_sy.rb b/test/rss/test_maker_sy.rb index fd6df9a0eb..309a3b70fd 100644 --- a/test/rss/test_maker_sy.rb +++ b/test/rss/test_maker_sy.rb @@ -24,6 +24,7 @@ module RSS rss = RSS::Maker.make("1.0") do |maker| setup_dummy_channel(maker) set_elements(maker.channel) + setup_dummy_item(maker) end assert_syndication(@elements, rss.channel) end diff --git a/test/rss/test_maker_xml-stylesheet.rb b/test/rss/test_maker_xml-stylesheet.rb index c380f21dc3..81d97ddc0e 100644 --- a/test/rss/test_maker_xml-stylesheet.rb +++ b/test/rss/test_maker_xml-stylesheet.rb @@ -24,6 +24,7 @@ module RSS end setup_dummy_channel(maker) + setup_dummy_item(maker) end xss = rss.xml_stylesheets.first @@ -38,11 +39,12 @@ module RSS href = 'http://example.com/index.xsl' type = 'text/xsl' rss = RSS::Maker.make("1.0") do |maker| - maker.xml_stylesheets.new_xml_stylesheet do |xss| - xss.href = href + maker.xml_stylesheets.new_xml_stylesheet do |_xss| + _xss.href = href end setup_dummy_channel(maker) + setup_dummy_item(maker) end xss = rss.xml_stylesheets.first @@ -61,6 +63,7 @@ module RSS end setup_dummy_channel(maker) + setup_dummy_item(maker) end assert(rss.xml_stylesheets.empty?) @@ -71,6 +74,7 @@ module RSS end setup_dummy_channel(maker) + setup_dummy_item(maker) end assert(rss.xml_stylesheets.empty?) end diff --git a/test/rss/test_parser.rb b/test/rss/test_parser.rb index 49b441bf96..59458ef51b 100644 --- a/test/rss/test_parser.rb +++ b/test/rss/test_parser.rb @@ -1,14 +1,4 @@ -begin - require "fileutils" -rescue LoadError - module FileUtils - module_function - def rm_f(target) - File.unlink(target) - rescue Errno::ENOENT - end - end -end +require "fileutils" require "rss-testcase" @@ -28,7 +18,7 @@ EOR @rss_file = "rss10.rdf" File.open(@rss_file, "w") {|f| f.print(@rss10)} end - + def teardown Parser.default_parser = @_default_parser FileUtils.rm_f(@rss_file) diff --git a/test/rss/test_parser_1.0.rb b/test/rss/test_parser_1.0.rb index 472602b04a..819ce286a5 100644 --- a/test/rss/test_parser_1.0.rb +++ b/test/rss/test_parser_1.0.rb @@ -498,6 +498,14 @@ EOR Parser.parse(rss, true, false) end end + + def test_unknown_duplicated_element + xmlns = {"test" => "http://localhost/test"} + assert_parse(make_RDF(<<-EOR, xmlns), :nothing_raised) + #{make_channel("<test:string/>")} + #{make_item} + #{make_image} + EOR + end end end - diff --git a/test/rss/test_parser_atom_entry.rb b/test/rss/test_parser_atom_entry.rb new file mode 100644 index 0000000000..c2572d7a3b --- /dev/null +++ b/test/rss/test_parser_atom_entry.rb @@ -0,0 +1,163 @@ +require "rss-testcase" + +require "rss/atom" + +module RSS + class TestParserAtom < TestCase + def test_entry_validation + assert_ns("", Atom::URI) do + Parser.parse(<<-EOA) +<entry/> +EOA + end + + assert_ns("", Atom::URI) do + Parser.parse(<<-EOA) +<entry xmlns="hoge"/> +EOA + end + + assert_parse(<<-EOA, :missing_tag, "id", "entry") do +<entry xmlns="#{Atom::URI}"/> +EOA + end + + assert_parse(<<-EOA, :missing_tag, "title", "entry") do +<entry xmlns="#{Atom::URI}"> + <id>urn:uuid:506e336c-a26e-4457-917b-b89dca7ae746</id> +</entry> +EOA + end + + assert_parse(<<-EOA, :missing_tag, "updated", "entry") do +<entry xmlns="#{Atom::URI}"> + <id>urn:uuid:506e336c-a26e-4457-917b-b89dca7ae746</id> + <title>Example Entry</title> +</entry> +EOA + end + + assert_parse(<<-EOA, :missing_tag, "author", "entry") do +<entry xmlns="#{Atom::URI}"> + <id>urn:uuid:506e336c-a26e-4457-917b-b89dca7ae746</id> + <title>Example Entry</title> + <updated>2003-10-10T18:30:02Z</updated> +</entry> +EOA + end + + assert_parse(<<-EOA, :nothing_raised) do +<entry xmlns="#{Atom::URI}"> + <id>urn:uuid:506e336c-a26e-4457-917b-b89dca7ae746</id> + <title>Example Entry</title> + <updated>2003-10-10T18:30:02Z</updated> + <author> + <name>A person</name> + </author> +</entry> +EOA + end + end + + def test_entry + entry = RSS::Parser.parse(<<-EOA) +<?xml version="1.0" encoding="utf-8"?> +<entry xmlns="http://www.w3.org/2005/Atom"> + <author> + <name>A person</name> + </author> + <title>Atom-Powered Robots Run Amok</title> + <link href="http://example.org/2003/12/13/atom03"/> + <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id> + <updated>2003-12-13T18:30:02Z</updated> + <summary>Some text.</summary> +</entry> +EOA + assert_not_nil(entry) + assert_equal("Atom-Powered Robots Run Amok", entry.title.content) + assert_equal("http://example.org/2003/12/13/atom03", entry.link.href) + assert_equal("urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a", + entry.id.content) + assert_equal(Time.parse("2003-12-13T18:30:02Z"), entry.updated.content) + assert_equal("Some text.", entry.summary.content) + end + + def test_entry_author + assert_atom_person("author", method(:make_entry_document)) do |entry| + assert_equal(2, entry.authors.size) + entry.authors.last + end + end + + def test_entry_category + assert_atom_category(method(:make_entry_document)) do |entry| + assert_equal(1, entry.categories.size) + entry.category + end + end + + def test_entry_content_text + assert_atom_content(method(:make_entry_document)) do |entry| + entry.content + end + end + + def test_entry_contributor + assert_atom_person("contributor", method(:make_entry_document)) do |entry| + assert_equal(1, entry.contributors.size) + entry.contributor + end + end + + def test_entry_id + entry = RSS::Parser.parse(make_entry_document) + assert_equal(ENTRY_ID, entry.id.content) + end + + def test_entry_link + assert_atom_link(method(:make_entry_document)) do |entry| + assert_equal(1, entry.links.size) + entry.link + end + end + + def test_published + generator = method(:make_entry_document) + assert_atom_date_construct("published", generator) do |entry| + entry.published + end + end + + def test_entry_rights + generator = method(:make_entry_document) + assert_atom_text_construct("rights", generator) do |entry| + entry.rights + end + end + + def test_entry_source + generator = method(:make_entry_document_with_open_source) + assert_atom_source(generator) do |entry| + assert_not_nil(entry.source) + entry.source + end + end + + def test_entry_summary + generator = method(:make_entry_document) + assert_atom_text_construct("summary", generator) do |entry| + entry.summary + end + end + + def test_entry_title + entry = RSS::Parser.parse(make_entry_document) + assert_equal(ENTRY_TITLE, entry.title.content) + end + + def test_entry_updated + entry = RSS::Parser.parse(make_entry_document) + assert_equal(Time.parse(ENTRY_UPDATED), entry.updated.content) + end + end +end diff --git a/test/rss/test_parser_atom_feed.rb b/test/rss/test_parser_atom_feed.rb new file mode 100644 index 0000000000..4358cc8898 --- /dev/null +++ b/test/rss/test_parser_atom_feed.rb @@ -0,0 +1,276 @@ +require "rss-testcase" + +require "rss/atom" + +module RSS + class TestParserAtomFeed < TestCase + def test_feed_validation + assert_ns("", Atom::URI) do + Parser.parse(<<-EOA) +<feed/> +EOA + end + + assert_ns("", Atom::URI) do + Parser.parse(<<-EOA) +<feed xmlns="hoge"/> +EOA + end + + assert_parse(<<-EOA, :missing_tag, "id", "feed") do +<feed xmlns="#{Atom::URI}"/> +EOA + end + + assert_parse(<<-EOA, :missing_tag, "title", "feed") do +<feed xmlns="#{Atom::URI}"> + <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id> +</feed> +EOA + end + + assert_parse(<<-EOA, :missing_tag, "updated", "feed") do +<feed xmlns="#{Atom::URI}"> + <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id> + <title>Example Feed</title> +</feed> +EOA + end + + assert_parse(<<-EOA, :missing_tag, "author", "feed") do +<feed xmlns="#{Atom::URI}"> + <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id> + <title>Example Feed</title> + <updated>2003-12-13T18:30:02Z</updated> +</feed> +EOA + end + + assert_parse(<<-EOA, :nothing_raised) do +<feed xmlns="#{Atom::URI}"> + <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id> + <title>Example Feed</title> + <updated>2003-12-13T18:30:02Z</updated> + <author> + <name>A person</name> + </author> +</feed> +EOA + end + end + + def test_lang + feed = RSS::Parser.parse(<<-EOA) +<feed xmlns="#{Atom::URI}" xml:lang="ja"> + <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id> + <title xml:lang="en">Example Feed</title> + <updated>2003-12-13T18:30:02Z</updated> + <author xml:lang="en"> + <name>A person</name> + </author> +</feed> +EOA + + assert_equal("ja", feed.lang) + assert_equal("ja", feed.id.lang) + assert_equal("en", feed.title.lang) + assert_equal("ja", feed.updated.lang) + assert_equal("en", feed.author.lang) + assert_equal("en", feed.author.name.lang) + end + + def test_base + feed = RSS::Parser.parse(<<-EOA) +<feed xmlns="#{Atom::URI}" xml:base="http://example.com/"> + <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id> + <title xml:lang="en">Example Feed</title> + <updated>2003-12-13T18:30:02Z</updated> + <generator uri="generator">Generator</generator> + <link hreflang="ja" href="http://example.org/link1"/> + <link hreflang="en" href="link2"/> + <link hreflang="fr" xml:base="http://example.net/" href="link3"/> + <author> + <name>A person</name> + <uri>person</uri> + </author> +</feed> +EOA + + assert_equal("http://example.com/", feed.base) + assert_equal("http://example.com/", feed.id.base) + assert_equal("http://example.com/", feed.title.base) + assert_equal("http://example.com/", feed.updated.base) + assert_equal("http://example.com/", feed.generator.base) + assert_equal("http://example.com/generator", feed.generator.uri) + + assert_equal("http://example.com/", feed.links[0].base) + assert_equal("http://example.org/link1", feed.links[0].href) + assert_equal("http://example.com/", feed.links[1].base) + assert_equal("http://example.com/link2", feed.links[1].href) + assert_equal("http://example.net/", feed.links[2].base) + assert_equal("http://example.net/link3", feed.links[2].href) + assert_equal("http://example.com/person", feed.author.uri.content) + end + + def test_feed_author + assert_atom_person("author", method(:make_feed)) do |feed| + assert_equal(2, feed.authors.size) + feed.authors[1] + end + end + + def test_entry_author + generator = method(:make_feed_with_open_entry) + assert_atom_person("author", generator) do |feed| + assert_equal(1, feed.entries.size) + assert_equal(1, feed.entry.authors.size) + feed.entry.author + end + end + + def test_feed_category + assert_atom_category(method(:make_feed)) do |feed| + assert_equal(1, feed.categories.size) + feed.category + end + end + + def test_entry_category + assert_atom_category(method(:make_feed_with_open_entry)) do |feed| + assert_equal(1, feed.entries.size) + assert_equal(1, feed.entry.categories.size) + feed.entry.category + end + end + + def test_entry_content + assert_atom_content(method(:make_feed_with_open_entry)) do |feed| + assert_equal(1, feed.entries.size) + feed.entry.content + end + end + + def test_feed_contributor + assert_atom_person("contributor", method(:make_feed)) do |feed| + assert_equal(1, feed.contributors.size) + feed.contributor + end + end + + def test_entry_contributor + generator = method(:make_feed_with_open_entry) + assert_atom_person("contributor", generator) do |feed| + assert_equal(1, feed.entries.size) + assert_equal(1, feed.entry.contributors.size) + feed.entry.contributor + end + end + + def test_feed_generator + assert_atom_generator(method(:make_feed)) do |feed| + feed.generator + end + end + + def test_feed_icon + assert_atom_icon(method(:make_feed)) do |feed| + feed.icon + end + end + + def test_feed_id + feed = RSS::Parser.parse(make_feed('')) + assert_equal(FEED_ID, feed.id.content) + end + + def test_entry_id + feed = RSS::Parser.parse(make_feed('')) + assert_equal(ENTRY_ID, feed.entry.id.content) + end + + def test_feed_link + assert_atom_link(method(:make_feed)) do |feed| + assert_equal(1, feed.links.size) + feed.link + end + end + + def test_entry_link + assert_atom_link(method(:make_feed_with_open_entry)) do |feed| + assert_equal(1, feed.entries.size) + assert_equal(1, feed.entry.links.size) + feed.entry.link + end + end + + def test_feed_logo + assert_atom_logo(method(:make_feed)) do |feed| + feed.logo + end + end + + def test_feed_rights + assert_atom_text_construct("rights", method(:make_feed)) do |feed| + feed.rights + end + end + + def test_entry_rights + generator = method(:make_feed_with_open_entry) + assert_atom_text_construct("rights", generator) do |feed| + assert_equal(1, feed.entries.size) + feed.entry.rights + end + end + + def test_entry_source + assert_atom_source(method(:make_feed_with_open_entry_source)) do |feed| + assert_equal(1, feed.entries.size) + assert_not_nil(feed.entry.source) + feed.entry.source + end + end + + def test_feed_subtitle + assert_atom_text_construct("subtitle", method(:make_feed)) do |feed| + feed.subtitle + end + end + + def test_feed_title + feed = RSS::Parser.parse(make_feed('')) + assert_equal(FEED_TITLE, feed.title.content) + end + + def test_entry_title + feed = RSS::Parser.parse(make_feed('')) + assert_equal(ENTRY_TITLE, feed.entry.title.content) + end + + def test_feed_updated + feed = RSS::Parser.parse(make_feed('')) + assert_equal(Time.parse(FEED_UPDATED), feed.updated.content) + end + + def test_entry_updated + feed = RSS::Parser.parse(make_feed('')) + assert_equal(Time.parse(ENTRY_UPDATED), feed.entry.updated.content) + end + + def test_entry_published + generator = method(:make_feed_with_open_entry) + assert_atom_date_construct("published", generator) do |feed| + assert_equal(1, feed.entries.size) + feed.entry.published + end + end + + def test_entry_summary + generator = method(:make_feed_with_open_entry) + assert_atom_text_construct("summary", generator) do |feed| + assert_equal(1, feed.entries.size) + feed.entry.summary + end + end + end +end diff --git a/test/rss/test_setup_maker_0.9.rb b/test/rss/test_setup_maker_0.9.rb index 9408728371..c20186cc8a 100644 --- a/test/rss/test_setup_maker_0.9.rb +++ b/test/rss/test_setup_maker_0.9.rb @@ -49,10 +49,19 @@ module RSS new_hour.content = hour end end + + setup_dummy_image(maker) + end + + assert_not_set_error("maker.image", %w(title url)) do + RSS::Maker.make("0.91") do |maker| + rss.channel.setup_maker(maker) + end end new_rss = RSS::Maker.make("0.91") do |maker| rss.channel.setup_maker(maker) + setup_dummy_image(maker) end channel = new_rss.channel @@ -74,9 +83,8 @@ module RSS skipHours.each_with_index do |hour, i| assert_equal(hour.to_i, channel.skipHours.hours[i].content) end - + assert(channel.items.empty?) - assert_nil(channel.image) assert_nil(channel.textInput) end @@ -121,6 +129,7 @@ module RSS rss = RSS::Maker.make("0.91") do |maker| setup_dummy_channel(maker) + setup_dummy_image(maker) maker.textinput.title = title maker.textinput.description = description @@ -130,6 +139,7 @@ module RSS new_rss = RSS::Maker.make("0.91") do |maker| rss.channel.setup_maker(maker) + rss.image.setup_maker(maker) rss.textinput.setup_maker(maker) end @@ -157,6 +167,8 @@ module RSS item.description = "#{description}#{i}" end end + + setup_dummy_image(maker) end new_rss = RSS::Maker.make("0.91") do |maker| @@ -169,15 +181,16 @@ module RSS item.setup_maker(maker.items) end end + + rss.image.setup_maker(maker) end - + assert_equal(item_size, new_rss.items.size) new_rss.items.each_with_index do |item, i| assert_equal("#{title}#{i}", item.title) assert_equal("#{link}#{i}", item.link) assert_equal("#{description}#{i}", item.description) end - end def test_setup_maker_items_backward_compatibility @@ -209,8 +222,9 @@ module RSS end setup_dummy_channel(maker) + setup_dummy_image(maker) end - + new_rss = RSS::Maker.make("0.91") do |maker| rss.setup_maker(maker) end @@ -228,6 +242,5 @@ module RSS assert_equal(charset, xss.charset) assert_equal(alternate, xss.alternate) end - end end diff --git a/test/rss/test_setup_maker_1.0.rb b/test/rss/test_setup_maker_1.0.rb index 51a4d146c7..0b960d91c8 100644 --- a/test/rss/test_setup_maker_1.0.rb +++ b/test/rss/test_setup_maker_1.0.rb @@ -87,10 +87,15 @@ module RSS @sy_elems.each do |var, value| maker.channel.__send__("sy_#{var}=", value) end + + setup_dummy_item(maker) end new_rss = RSS::Maker.make("1.0") do |maker| rss.channel.setup_maker(maker) + rss.items.each do |item| + item.setup_maker(maker) + end end channel = new_rss.channel @@ -98,7 +103,7 @@ module RSS assert_equal(title, channel.title) assert_equal(link, channel.link) assert_equal(description, channel.description) - assert_equal(true, channel.items.Seq.lis.empty?) + assert_equal(1, channel.items.Seq.lis.size) assert_nil(channel.image) assert_nil(channel.textinput) @@ -128,11 +133,16 @@ module RSS @dc_elems.each do |var, value| maker.image.__send__("dc_#{var}=", value) end + + setup_dummy_item(maker) end new_rss = RSS::Maker.make("1.0") do |maker| rss.channel.setup_maker(maker) rss.image.setup_maker(maker) + rss.items.each do |item| + item.setup_maker(maker) + end end image = new_rss.image @@ -164,11 +174,16 @@ module RSS @dc_elems.each do |var, value| maker.textinput.__send__("dc_#{var}=", value) end + + setup_dummy_item(maker) end new_rss = RSS::Maker.make("1.0") do |maker| rss.channel.setup_maker(maker) rss.textinput.setup_maker(maker) + rss.items.each do |item| + item.setup_maker(maker) + end end textinput = new_rss.textinput @@ -247,8 +262,8 @@ module RSS assert_equal(@trackback_elems[:ping], item.trackback_ping) assert_equal(@trackback_elems[:about].size, item.trackback_abouts.size) - item.trackback_abouts.each_with_index do |about, i| - assert_equal(@trackback_elems[:about][i], about.value) + item.trackback_abouts.each_with_index do |about, j| + assert_equal(@trackback_elems[:about][j], about.value) end end end @@ -333,6 +348,7 @@ module RSS end setup_dummy_channel(maker) + setup_dummy_item(maker) end new_rss = RSS::Maker.make("1.0") do |maker| @@ -522,8 +538,8 @@ module RSS assert_equal(@trackback_elems[:ping], item.trackback_ping) assert_equal(@trackback_elems[:about].size, item.trackback_abouts.size) - item.trackback_abouts.each_with_index do |about, i| - assert_equal(@trackback_elems[:about][i], about.value) + item.trackback_abouts.each_with_index do |about, j| + assert_equal(@trackback_elems[:about][j], about.value) end end diff --git a/test/rss/test_setup_maker_atom_entry.rb b/test/rss/test_setup_maker_atom_entry.rb new file mode 100644 index 0000000000..6f3df65f3f --- /dev/null +++ b/test/rss/test_setup_maker_atom_entry.rb @@ -0,0 +1,409 @@ +require "rss-testcase" + +require "rss/maker" + +module RSS + class TestSetupMakerAtomEntry < TestCase + def setup + t = Time.iso8601("2000-01-01T12:00:05+00:00") + class << t + alias_method(:to_s, :iso8601) + end + + @dc_elems = { + :title => "hoge", + :description => + " XML is placing increasingly heavy loads on + the existing technical infrastructure of the Internet.", + :creator => "Rael Dornfest (mailto:rael@oreilly.com)", + :subject => "XML", + :publisher => "The O'Reilly Network", + :contributor => "hogehoge", + :type => "fugafuga", + :format => "hohoho", + :identifier => "fufufu", + :source => "barbar", + :language => "ja", + :relation => "cococo", + :rights => "Copyright (c) 2000 O'Reilly & Associates, Inc.", + :date => t, + } + end + + def test_setup_maker_entry(with_dc=true) + authors = [ + { + :name => "Bob", + :uri => "http://example.com/~bob/", + :email => "bob@example.com", + }, + { + :name => "Alice", + :uri => "http://example.com/~alice/", + :email => "alice@example.com", + }, + ] + categories = [ + { + :term => "music", + :label => "Music", + }, + { + :term => "book", + :scheme => "http://example.com/category/book/", + :label => "Book", + }, + ] + contributors = [ + { + :name => "Chris", + :email => "chris@example.com", + }, + { + :name => "Eva", + :uri => "http://example.com/~eva/", + }, + ] + id = "urn:uuid:8b105336-7e20-45fc-bb78-37fb3e1db25a" + link = "http://hoge.com" + published = Time.now - 60 * 3600 + rights = "Copyrights (c) 2007 Alice and Bob" + description = "fugafugafugafuga" + title = "fugafuga" + updated = Time.now + + feed = RSS::Maker.make("atom:entry") do |maker| + maker.items.new_item do |item| + authors.each do |author_info| + item.authors.new_author do |author| + author_info.each do |key, value| + author.__send__("#{key}=", value) + end + end + end + + categories.each do |category_info| + item.categories.new_category do |category| + category_info.each do |key, value| + category.__send__("#{key}=", value) + end + end + end + + contributors.each do |contributor_info| + item.contributors.new_contributor do |contributor| + contributor_info.each do |key, value| + contributor.__send__("#{key}=", value) + end + end + end + + item.id = id + item.link = link + item.published = published + item.rights = rights + item.description = description + item.title = title + item.updated = updated + + if with_dc + @dc_elems.each do |var, value| + if var == :date + item.new_dc_date(value) + else + item.__send__("dc_#{var}=", value) + end + end + end + end + end + assert_not_nil(feed) + + new_feed = RSS::Maker.make("atom:entry") do |maker| + feed.setup_maker(maker) + end + assert_not_nil(new_feed) + + new_authors = new_feed.authors.collect do |author| + { + :name => author.name.content, + :uri => author.uri.content, + :email => author.email.content, + } + end + assert_equal(authors, new_authors) + + new_categories = new_feed.categories.collect do |category| + { + :term => category.term, + :scheme => category.scheme, + :label => category.label, + }.reject {|key, value| value.nil?} + end + assert_equal(categories, new_categories) + + new_contributors = new_feed.contributors.collect do |contributor| + info = {} + info[:name] = contributor.name.content + info[:uri] = contributor.uri.content if contributor.uri + info[:email] = contributor.email.content if contributor.email + info + end + assert_equal(contributors, new_contributors) + + assert_equal(id, new_feed.id.content) + assert_equal(link, new_feed.link.href) + assert_equal(published, new_feed.published.content) + assert_equal(rights, new_feed.rights.content) + assert_equal(description, new_feed.summary.content) + assert_equal(title, new_feed.title.content) + assert_equal(updated, new_feed.updated.content) + + if with_dc + @dc_elems.each do |var, value| + if var == :date + assert_equal([updated, value], + new_feed.dc_dates.collect {|date| date.value}) + else + assert_equal(value, new_feed.__send__("dc_#{var}")) + end + end + end + + assert_equal(1, new_feed.items.size) + end + + def test_setup_maker_entry_without_dc + test_setup_maker_entry(false) + end + + def test_setup_maker_items(for_backward_compatibility=false) + title = "TITLE" + link = "http://hoge.com/" + description = "text hoge fuga" + updated = Time.now + + item_size = 5 + feed = RSS::Maker.make("atom:entry") do |maker| + setup_dummy_channel_atom(maker) + + item_size.times do |i| + maker.items.new_item do |item| + item.title = "#{title}#{i}" + item.link = "#{link}#{i}" + item.description = "#{description}#{i}" + item.updated = updated + i * 60 + end + end + end + + new_feed = RSS::Maker.make("atom:entry") do |maker| + feed.items.each do |item| + if for_backward_compatibility + item.setup_maker(maker) + else + item.setup_maker(maker.items) + end + end + + feed.items.clear + feed.setup_maker(maker) + end + + assert_equal(1, new_feed.items.size) + new_feed.items[0..1].each_with_index do |item, i| + assert_equal("#{title}#{i}", item.title.content) + assert_equal("#{link}#{i}", item.link.href) + assert_equal("#{description}#{i}", item.summary.content) + assert_equal(updated + i * 60, item.updated.content) + end + end + + def test_setup_maker_items_sort + title = "TITLE" + link = "http://hoge.com/" + summary = "text hoge fuga" + updated = Time.now + + feed_size = 5 + feed = RSS::Maker.make("atom:entry") do |maker| + setup_dummy_channel_atom(maker) + + feed_size.times do |i| + entry_class = RSS::Atom::Entry + entry = entry_class.new + entry.title = entry_class::Title.new(:content => "#{title}#{i}") + entry.links << entry_class::Link.new(:href => "#{link}#{i}") + entry.summary = entry_class::Summary.new(:content => "#{summary}#{i}") + entry.updated = entry_class::Updated.new(:content => updated + i * 60) + entry.setup_maker(maker.items) + end + maker.items.do_sort = false + end + assert_equal(1, feed.items.size) + + assert_equal("#{title}0", feed.title.content) + assert_equal("#{link}0", feed.link.href) + assert_equal("#{summary}0", feed.summary.content) + + + feed = RSS::Maker.make("atom:entry") do |maker| + setup_dummy_channel_atom(maker) + + feed_size.times do |i| + entry_class = RSS::Atom::Entry + entry = entry_class.new + entry.title = entry_class::Title.new(:content => "#{title}#{i}") + entry.links << entry_class::Link.new(:href => "#{link}#{i}") + entry.summary = entry_class::Summary.new(:content => "#{summary}#{i}") + entry.updated = entry_class::Updated.new(:content => updated + i * 60) + entry.setup_maker(maker.items) + end + maker.items.do_sort = true + end + assert_equal(1, feed.items.size) + + assert_equal("#{title}#{feed_size - 1}", feed.title.content) + assert_equal("#{link}#{feed_size - 1}", feed.link.href) + assert_equal("#{summary}#{feed_size - 1}", feed.summary.content) + end + + def test_setup_maker_items_backward_compatibility + test_setup_maker_items(true) + end + + def test_setup_maker + encoding = "EUC-JP" + standalone = true + + href = 'a.xsl' + type = 'text/xsl' + title = 'sample' + media = 'printer' + charset = 'UTF-8' + alternate = 'yes' + + feed = RSS::Maker.make("atom:entry") do |maker| + maker.encoding = encoding + maker.standalone = standalone + + maker.xml_stylesheets.new_xml_stylesheet do |xss| + xss.href = href + xss.type = type + xss.title = title + xss.media = media + xss.charset = charset + xss.alternate = alternate + end + + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + assert_not_nil(feed) + + new_feed = RSS::Maker.make("atom:entry") do |maker| + feed.setup_maker(maker) + end + + assert_equal(["atom", "1.0", "entry"], new_feed.feed_info) + assert_equal(encoding, new_feed.encoding) + assert_equal(standalone, new_feed.standalone) + + xss = new_feed.xml_stylesheets.first + assert_equal(1, new_feed.xml_stylesheets.size) + assert_equal(href, xss.href) + assert_equal(type, xss.type) + assert_equal(title, xss.title) + assert_equal(media, xss.media) + assert_equal(charset, xss.charset) + assert_equal(alternate, xss.alternate) + end + + def test_setup_maker_full + encoding = "EUC-JP" + standalone = true + + href = 'a.xsl' + type = 'text/xsl' + title = 'sample' + media = 'printer' + charset = 'UTF-8' + alternate = 'yes' + + channel_about = "http://hoge.com" + channel_title = "fugafuga" + channel_link = "http://hoge.com" + channel_description = "fugafugafugafuga" + channel_author = "Bob" + + image_url = "http://hoge.com/hoge.png" + + item_title = "TITLE" + item_link = "http://hoge.com/" + item_description = "text hoge fuga" + + entry_size = 5 + feed = RSS::Maker.make("atom:entry") do |maker| + maker.encoding = encoding + maker.standalone = standalone + + maker.xml_stylesheets.new_xml_stylesheet do |xss| + xss.href = href + xss.type = type + xss.title = title + xss.media = media + xss.charset = charset + xss.alternate = alternate + end + + maker.channel.about = channel_about + maker.channel.title = channel_title + maker.channel.link = channel_link + maker.channel.description = channel_description + maker.channel.author = channel_author + @dc_elems.each do |var, value| + maker.channel.__send__("dc_#{var}=", value) + end + + maker.image.url = image_url + + entry_size.times do |i| + maker.items.new_item do |item| + item.title = "#{item_title}#{i}" + item.link = "#{item_link}#{i}" + item.description = "#{item_description}#{i}" + + @dc_elems.each do |var, value| + item.__send__("dc_#{var}=", value) + end + end + end + end + + new_feed = RSS::Maker.make("atom:entry") do |maker| + feed.setup_maker(maker) + end + + assert_equal(["atom", "1.0", "entry"], new_feed.feed_info) + assert_equal(encoding, new_feed.encoding) + assert_equal(standalone, new_feed.standalone) + + xss = new_feed.xml_stylesheets.first + assert_equal(1, new_feed.xml_stylesheets.size) + assert_equal(href, xss.href) + assert_equal(type, xss.type) + assert_equal(title, xss.title) + assert_equal(media, xss.media) + assert_equal(charset, xss.charset) + assert_equal(alternate, xss.alternate) + + assert_equal("#{item_title}0", new_feed.title.content) + assert_equal("#{item_link}0", new_feed.link.href) + assert_equal("#{item_description}0", new_feed.summary.content) + @dc_elems.each do |var, value| + assert_equal(value, new_feed.__send__("dc_#{var}")) + end + assert_equal(1, new_feed.items.size) + end + end +end diff --git a/test/rss/test_setup_maker_atom_feed.rb b/test/rss/test_setup_maker_atom_feed.rb new file mode 100644 index 0000000000..03d33dbb9c --- /dev/null +++ b/test/rss/test_setup_maker_atom_feed.rb @@ -0,0 +1,445 @@ +require "rss-testcase" + +require "rss/maker" + +module RSS + class TestSetupMakerAtomFeed < TestCase + def setup + t = Time.iso8601("2000-01-01T12:00:05+00:00") + class << t + alias_method(:to_s, :iso8601) + end + + @dc_elems = { + :title => "hoge", + :description => + " XML is placing increasingly heavy loads on + the existing technical infrastructure of the Internet.", + :creator => "Rael Dornfest (mailto:rael@oreilly.com)", + :subject => "XML", + :publisher => "The O'Reilly Network", + :contributor => "hogehoge", + :type => "fugafuga", + :format => "hohoho", + :identifier => "fufufu", + :source => "barbar", + :language => "ja", + :relation => "cococo", + :rights => "Copyright (c) 2000 O'Reilly & Associates, Inc.", + :date => t, + } + end + + def test_setup_maker_feed(with_dc=true) + authors = [ + { + :name => "Bob", + :uri => "http://example.com/~bob/", + :email => "bob@example.com", + }, + { + :name => "Alice", + :uri => "http://example.com/~alice/", + :email => "alice@example.com", + }, + ] + categories = [ + { + :term => "music", + :label => "Music", + }, + { + :term => "book", + :scheme => "http://example.com/category/book/", + :label => "Book", + }, + ] + contributors = [ + { + :name => "Chris", + :email => "chris@example.com", + }, + { + :name => "Eva", + :uri => "http://example.com/~eva/", + }, + ] + generator = { + :uri => "http://example.com/generator/", + :version => "0.0.1", + :content => "Feed Generator", + } + icon = "http://example.com/icon.png" + about = "http://hoge.com" + title = "fugafuga" + link = "http://hoge.com" + logo = "http://example.com/logo.png" + rights = "Copyrights (c) 2007 Alice and Bob" + description = "fugafugafugafuga" + updated = Time.now + + feed = RSS::Maker.make("atom") do |maker| + authors.each do |author_info| + maker.channel.authors.new_author do |author| + author_info.each do |key, value| + author.__send__("#{key}=", value) + end + end + end + + categories.each do |category_info| + maker.channel.categories.new_category do |category| + category_info.each do |key, value| + category.__send__("#{key}=", value) + end + end + end + + contributors.each do |contributor_info| + maker.channel.contributors.new_contributor do |contributor| + contributor_info.each do |key, value| + contributor.__send__("#{key}=", value) + end + end + end + + generator.each do |key, value| + maker.channel.generator do |g| + g.__send__("#{key}=", value) + end + end + + maker.channel.icon = icon + + maker.channel.about = about + maker.channel.link = link + maker.channel.logo = logo + maker.channel.rights = rights + maker.channel.title = title + maker.channel.description = description + maker.channel.updated = updated + + if with_dc + @dc_elems.each do |var, value| + if var == :date + maker.channel.new_dc_date(value) + else + maker.channel.__send__("dc_#{var}=", value) + end + end + end + + setup_dummy_item_atom(maker) + end + assert_not_nil(feed) + + new_feed = RSS::Maker.make("atom") do |maker| + feed.setup_maker(maker) + end + assert_not_nil(new_feed) + + new_authors = new_feed.authors.collect do |author| + { + :name => author.name.content, + :uri => author.uri.content, + :email => author.email.content, + } + end + assert_equal(authors, new_authors) + + new_categories = new_feed.categories.collect do |category| + { + :term => category.term, + :scheme => category.scheme, + :label => category.label, + }.reject {|key, value| value.nil?} + end + assert_equal(categories, new_categories) + + new_contributors = new_feed.contributors.collect do |contributor| + info = {} + info[:name] = contributor.name.content + info[:uri] = contributor.uri.content if contributor.uri + info[:email] = contributor.email.content if contributor.email + info + end + assert_equal(contributors, new_contributors) + + new_generator = { + :uri => new_feed.generator.uri, + :version => new_feed.generator.version, + :content => new_feed.generator.content, + } + assert_equal(generator, new_generator) + + assert_equal(icon, new_feed.icon.content) + assert_equal(about, new_feed.id.content) + assert_equal(link, new_feed.link.href) + assert_equal(logo, new_feed.logo.content) + assert_equal(rights, new_feed.rights.content) + assert_equal(description, new_feed.subtitle.content) + assert_equal(title, new_feed.title.content) + assert_equal(updated, new_feed.updated.content) + + if with_dc + @dc_elems.each do |var, value| + if var == :date + assert_equal([updated, value], + new_feed.dc_dates.collect {|date| date.value}) + else + assert_equal(value, new_feed.__send__("dc_#{var}")) + end + end + end + + assert_equal(1, new_feed.items.size) + end + + def test_setup_maker_feed_without_dc + test_setup_maker_feed(false) + end + + def test_setup_maker_items(for_backward_compatibility=false) + title = "TITLE" + link = "http://hoge.com/" + description = "text hoge fuga" + updated = Time.now + + item_size = 5 + feed = RSS::Maker.make("atom") do |maker| + setup_dummy_channel_atom(maker) + + item_size.times do |i| + maker.items.new_item do |item| + item.title = "#{title}#{i}" + item.link = "#{link}#{i}" + item.description = "#{description}#{i}" + item.updated = updated + i * 60 + end + end + end + + new_feed = RSS::Maker.make("atom") do |maker| + feed.items.each do |item| + if for_backward_compatibility + item.setup_maker(maker) + else + item.setup_maker(maker.items) + end + end + + feed.items.clear + feed.setup_maker(maker) + end + + assert_equal(item_size, new_feed.items.size) + new_feed.items.each_with_index do |item, i| + assert_equal("#{title}#{i}", item.title.content) + assert_equal("#{link}#{i}", item.link.href) + assert_equal("#{description}#{i}", item.summary.content) + assert_equal(updated + i * 60, item.updated.content) + end + end + + def test_setup_maker_items_sort + title = "TITLE" + link = "http://hoge.com/" + summary = "text hoge fuga" + updated = Time.now + + feed_size = 5 + feed = RSS::Maker.make("atom") do |maker| + setup_dummy_channel_atom(maker) + + feed_size.times do |i| + entry_class = RSS::Atom::Feed::Entry + entry = entry_class.new + entry.title = entry_class::Title.new(:content => "#{title}#{i}") + entry.links << entry_class::Link.new(:href => "#{link}#{i}") + entry.summary = entry_class::Summary.new(:content => "#{summary}#{i}") + entry.updated = entry_class::Updated.new(:content => updated + i * 60) + entry.setup_maker(maker.items) + end + maker.items.do_sort = false + end + assert_equal(feed_size, feed.entries.size) + feed.entries.each_with_index do |entry, i| + assert_equal("#{title}#{i}", entry.title.content) + assert_equal("#{link}#{i}", entry.link.href) + assert_equal("#{summary}#{i}", entry.summary.content) + end + + + feed = RSS::Maker.make("atom") do |maker| + setup_dummy_channel_atom(maker) + + feed_size.times do |i| + entry_class = RSS::Atom::Feed::Entry + entry = entry_class.new + entry.title = entry_class::Title.new(:content => "#{title}#{i}") + entry.links << entry_class::Link.new(:href => "#{link}#{i}") + entry.summary = entry_class::Summary.new(:content => "#{summary}#{i}") + entry.updated = entry_class::Updated.new(:content => updated + i * 60) + entry.setup_maker(maker.items) + end + maker.items.do_sort = true + end + assert_equal(feed_size, feed.entries.size) + feed.entries.reverse.each_with_index do |entry, i| + assert_equal("#{title}#{i}", entry.title.content) + assert_equal("#{link}#{i}", entry.link.href) + assert_equal("#{summary}#{i}", entry.summary.content) + end + end + + def test_setup_maker_items_backward_compatibility + test_setup_maker_items(true) + end + + def test_setup_maker + encoding = "EUC-JP" + standalone = true + + href = 'a.xsl' + type = 'text/xsl' + title = 'sample' + media = 'printer' + charset = 'UTF-8' + alternate = 'yes' + + feed = RSS::Maker.make("atom") do |maker| + maker.encoding = encoding + maker.standalone = standalone + + maker.xml_stylesheets.new_xml_stylesheet do |xss| + xss.href = href + xss.type = type + xss.title = title + xss.media = media + xss.charset = charset + xss.alternate = alternate + end + + setup_dummy_channel_atom(maker) + setup_dummy_item_atom(maker) + end + assert_not_nil(feed) + + new_feed = RSS::Maker.make("atom") do |maker| + feed.setup_maker(maker) + end + + assert_equal(["atom", "1.0", "feed"], new_feed.feed_info) + assert_equal(encoding, new_feed.encoding) + assert_equal(standalone, new_feed.standalone) + + xss = new_feed.xml_stylesheets.first + assert_equal(1, new_feed.xml_stylesheets.size) + assert_equal(href, xss.href) + assert_equal(type, xss.type) + assert_equal(title, xss.title) + assert_equal(media, xss.media) + assert_equal(charset, xss.charset) + assert_equal(alternate, xss.alternate) + end + + def test_setup_maker_full + encoding = "EUC-JP" + standalone = true + + href = 'a.xsl' + type = 'text/xsl' + title = 'sample' + media = 'printer' + charset = 'UTF-8' + alternate = 'yes' + + channel_about = "http://hoge.com" + channel_title = "fugafuga" + channel_link = "http://hoge.com" + channel_description = "fugafugafugafuga" + channel_author = "Bob" + + image_url = "http://hoge.com/hoge.png" + + item_title = "TITLE" + item_link = "http://hoge.com/" + item_description = "text hoge fuga" + + entry_size = 5 + feed = RSS::Maker.make("atom") do |maker| + maker.encoding = encoding + maker.standalone = standalone + + maker.xml_stylesheets.new_xml_stylesheet do |xss| + xss.href = href + xss.type = type + xss.title = title + xss.media = media + xss.charset = charset + xss.alternate = alternate + end + + maker.channel.about = channel_about + maker.channel.title = channel_title + maker.channel.link = channel_link + maker.channel.description = channel_description + maker.channel.author = channel_author + @dc_elems.each do |var, value| + maker.channel.__send__("dc_#{var}=", value) + end + + maker.image.url = image_url + + entry_size.times do |i| + maker.items.new_item do |item| + item.title = "#{item_title}#{i}" + item.link = "#{item_link}#{i}" + item.description = "#{item_description}#{i}" + + @dc_elems.each do |var, value| + item.__send__("dc_#{var}=", value) + end + end + end + end + + new_feed = RSS::Maker.make("atom") do |maker| + feed.setup_maker(maker) + end + + assert_equal(["atom", "1.0", "feed"], new_feed.feed_info) + assert_equal(encoding, new_feed.encoding) + assert_equal(standalone, new_feed.standalone) + + xss = new_feed.xml_stylesheets.first + assert_equal(1, new_feed.xml_stylesheets.size) + assert_equal(href, xss.href) + assert_equal(type, xss.type) + assert_equal(title, xss.title) + assert_equal(media, xss.media) + assert_equal(charset, xss.charset) + assert_equal(alternate, xss.alternate) + + assert_equal(channel_title, new_feed.title.content) + assert_equal(channel_link, new_feed.link.href) + assert_equal(channel_description, new_feed.subtitle.content) + assert_equal(channel_author, new_feed.author.name.content) + assert_equal(image_url, new_feed.logo.content) + @dc_elems.each do |var, value| + assert_equal(value, new_feed.__send__("dc_#{var}")) + end + + assert_equal(entry_size, new_feed.entries.size) + new_feed.entries.each_with_index do |entry, i| + assert_equal("#{item_title}#{i}", entry.title.content) + assert_equal("#{item_link}#{i}", entry.link.href) + assert_equal("#{item_description}#{i}", entry.summary.content) + + @dc_elems.each do |var, value| + assert_equal(value, entry.__send__("dc_#{var}")) + end + end + end + end +end diff --git a/test/rss/test_setup_maker_itunes.rb b/test/rss/test_setup_maker_itunes.rb new file mode 100644 index 0000000000..1f0372d6e7 --- /dev/null +++ b/test/rss/test_setup_maker_itunes.rb @@ -0,0 +1,144 @@ +require "rss-testcase" + +require "rss/maker" + +module RSS + class TestSetupMakerITunes < TestCase + def test_setup_maker_simple + author = "John Doe" + block = true + categories = ["Audio Blogs"] + image = "http://example.com/podcasts/everything/AllAboutEverything.jpg" + duration = "4:05" + duration_components = [0, 4, 5] + explicit = true + keywords = ["salt", "pepper", "shaker", "exciting"] + new_feed_url = "http://newlocation.com/example.rss" + owner = {:name => "John Doe", :email => "john.doe@example.com"} + subtitle = "A show about everything" + summary = "All About Everything is a show about " + + "everything. Each week we dive into any " + + "subject known to man and talk about it " + + "as much as we can. Look for our Podcast " + + "in the iTunes Music Store" + + feed = RSS::Maker.make("rss2.0") do |maker| + setup_dummy_channel(maker) + setup_dummy_item(maker) + + channel = maker.channel + channel.itunes_author = author + channel.itunes_block = block + categories.each do |category| + channel.itunes_categories.new_category.text = category + end + channel.itunes_image = image + channel.itunes_explicit = explicit + channel.itunes_keywords = keywords + channel.itunes_owner.itunes_name = owner[:name] + channel.itunes_owner.itunes_email = owner[:email] + channel.itunes_subtitle = subtitle + channel.itunes_summary = summary + + item = maker.items.last + item.itunes_author = author + item.itunes_block = block + item.itunes_duration = duration + item.itunes_explicit = explicit + item.itunes_keywords = keywords + item.itunes_subtitle = subtitle + item.itunes_summary = summary + end + assert_not_nil(feed) + + new_feed = RSS::Maker.make("rss2.0") do |maker| + feed.setup_maker(maker) + end + assert_not_nil(new_feed) + + channel = new_feed.channel + item = new_feed.items.last + + assert_equal(author, channel.itunes_author) + assert_equal(author, item.itunes_author) + + assert_equal(block, channel.itunes_block?) + assert_equal(block, item.itunes_block?) + + assert_equal(categories, + collect_itunes_categories(channel.itunes_categories)) + + assert_equal(image, channel.itunes_image.href) + + assert_equal(duration_components, + [item.itunes_duration.hour, + item.itunes_duration.minute, + item.itunes_duration.second]) + + assert_equal(explicit, channel.itunes_explicit?) + assert_equal(explicit, item.itunes_explicit?) + + assert_equal(keywords, channel.itunes_keywords) + assert_equal(keywords, item.itunes_keywords) + + assert_equal(owner, + { + :name => channel.itunes_owner.itunes_name, + :email => channel.itunes_owner.itunes_email + }) + + assert_equal(subtitle, channel.itunes_subtitle) + assert_equal(subtitle, item.itunes_subtitle) + + assert_equal(summary, channel.itunes_summary) + assert_equal(summary, item.itunes_summary) + end + + def test_setup_maker_with_nested_categories + categories = [["Arts & Entertainment", "Games"], + ["Technology", "Computers"], + "Audio Blogs"] + + feed = RSS::Maker.make("rss2.0") do |maker| + setup_dummy_channel(maker) + setup_dummy_item(maker) + + channel = maker.channel + categories.each do |category| + target = channel.itunes_categories + if category.is_a?(Array) + category.each do |sub_category| + target = target.new_category + target.text = sub_category + end + else + target.new_category.text = category + end + end + end + assert_not_nil(feed) + + new_feed = RSS::Maker.make("rss2.0") do |maker| + feed.setup_maker(maker) + end + assert_not_nil(new_feed) + + channel = new_feed.channel + + assert_equal(categories, + collect_itunes_categories(channel.itunes_categories)) + end + + private + def collect_itunes_categories(categories) + categories.collect do |c| + rest = collect_itunes_categories(c.itunes_categories) + if rest.empty? + c.text + else + [c.text, *rest] + end + end + end + end +end diff --git a/test/rss/test_setup_maker_slash.rb b/test/rss/test_setup_maker_slash.rb new file mode 100644 index 0000000000..07fa5bb342 --- /dev/null +++ b/test/rss/test_setup_maker_slash.rb @@ -0,0 +1,38 @@ +require "rss-testcase" + +require "rss/maker" + +module RSS + class TestSetupMakerSlash < TestCase + def test_setup_maker + elements = { + "section" => "articles", + "department" => "not-an-ocean-unless-there-are-lobsters", + "comments" => 177, + "hit_parades" => [177, 155, 105, 33, 6, 3, 0], + } + + rss = RSS::Maker.make("rss1.0") do |maker| + setup_dummy_channel(maker) + setup_dummy_item(maker) + + item = maker.items.last + item.slash_section = elements["section"] + item.slash_department = elements["department"] + item.slash_comments = elements["comments"] + item.slash_hit_parade = elements["hit_parades"].join(",") + end + assert_not_nil(rss) + + new_rss = RSS::Maker.make("rss1.0") do |maker| + rss.setup_maker(maker) + end + assert_not_nil(new_rss) + + item = new_rss.items.last + assert_not_nil(item) + + assert_slash_elements(elements, item) + end + end +end diff --git a/test/rss/test_slash.rb b/test/rss/test_slash.rb new file mode 100644 index 0000000000..aec0a868f5 --- /dev/null +++ b/test/rss/test_slash.rb @@ -0,0 +1,64 @@ +require "cgi" +require "rexml/document" + +require "rss-testcase" + +require "rss/1.0" +require "rss/slash" + +module RSS + class TestSlash < TestCase + def setup + @elements = { + "section" => "articles", + "department" => "not-an-ocean-unless-there-are-lobsters", + "comments" => 177, + "hit_parades" => [177, 155, 105, 33, 6, 3, 0], + } + + slash_nodes = @elements.collect do |name, value| + if name == "hit_parades" + name = "hit_parade" + value = value.join(",") + end + "<slash:#{name}>#{value}</slash:#{name}>" + end.join("\n") + + slash_ns = {"slash" => "http://purl.org/rss/1.0/modules/slash/"} + @source = make_RDF(<<-EOR, slash_ns) +#{make_channel} +#{make_image} +#{make_item(slash_nodes)} +#{make_textinput} +EOR + end + + def test_parser + rss = RSS::Parser.parse(@source) + + assert_not_nil(rss) + + item = rss.items[0] + assert_not_nil(item) + + assert_slash_elements(item) + end + + def test_to_s + rss = RSS::Parser.parse(@source) + rss = RSS::Parser.parse(rss.to_s) + + assert_not_nil(rss) + + item = rss.items[0] + assert_not_nil(item) + + assert_slash_elements(item) + end + + private + def assert_slash_elements(target) + super(@elements, target) + end + end +end diff --git a/test/rss/test_syndication.rb b/test/rss/test_syndication.rb index 697ff439e4..abd75e905e 100644 --- a/test/rss/test_syndication.rb +++ b/test/rss/test_syndication.rb @@ -93,7 +93,7 @@ EOR %w(-2 0.3 -0.4).each do |x| @parents.each do |parent| - assert_not_available_value("updateBase", x) do + assert_not_available_value("sy:updateBase", x) do @rss.__send__(parent).sy_updateBase = x end end @@ -120,8 +120,6 @@ EOR end end end - end - end end diff --git a/test/rss/test_taxonomy.rb b/test/rss/test_taxonomy.rb index 10ae55190a..5109f3d892 100644 --- a/test/rss/test_taxonomy.rb +++ b/test/rss/test_taxonomy.rb @@ -144,8 +144,10 @@ EOR end @topic_nodes.each_with_index do |node, i| - expected = REXML::Document.new(node).root - actual = REXML::Document.new(@rss.taxo_topics[i].to_s(true, "")).root + expected_xml = taxo_xmlns_container(node) + expected = REXML::Document.new(expected_xml).root.elements[1] + actual_xml = taxo_xmlns_container(@rss.taxo_topics[i].to_s(true, "")) + actual = REXML::Document.new(actual_xml).root.elements[1] expected_elems = expected.reject {|x| x.is_a?(REXML::Text)} actual_elems = actual.reject {|x| x.is_a?(REXML::Text)} expected_elems.sort! {|x, y| x.name <=> y.name} @@ -155,6 +157,16 @@ EOR assert_equal(expected.attributes.sort, actual.attributes.sort) end end + + private + def taxo_xmlns_container(content) + xmlns_container({ + @prefix => @uri, + "dc" => "http://purl.org/dc/elements/1.1/", + "rdf" => "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + }, + content) + end end end diff --git a/test/rss/test_to_s.rb b/test/rss/test_to_s.rb index 1538534034..66739591ea 100644 --- a/test/rss/test_to_s.rb +++ b/test/rss/test_to_s.rb @@ -12,7 +12,6 @@ require "rss/trackback" module RSS class TestToS < TestCase - def setup @image_url = "http://example.com/foo.png" @textinput_link = "http://example.com/search.cgi" @@ -435,6 +434,237 @@ module RSS new_about.value = about end end + + + def assert_channel10(attrs, channel) + _wrap_assertion do + n_attrs = normalized_attrs(attrs) + + names = %w(about title link description) + assert_attributes(attrs, names, channel) + + %w(image items textinput).each do |name| + value = n_attrs[name] + if value + target = channel.__send__(name) + __send__("assert_channel10_#{name}", value, target) + end + end + end + end + + def assert_channel10_image(attrs, image) + _wrap_assertion do + assert_attributes(attrs, %w(resource), image) + end + end + + def assert_channel10_textinput(attrs, textinput) + _wrap_assertion do + assert_attributes(attrs, %w(resource), textinput) + end + end + + def assert_channel10_items(attrs, items) + _wrap_assertion do + assert_equal(items.resources, items.Seq.lis.collect {|x| x.resource}) + items.Seq.lis.each_with_index do |li, i| + assert_attributes(attrs[i], %w(resource), li) + end + end + end + + def assert_image10(attrs, image) + _wrap_assertion do + names = %w(about title url link) + assert_attributes(attrs, names, image) + end + end + + def assert_items10(attrs, items) + _wrap_assertion do + names = %w(about title link description) + items.each_with_index do |item, i| + assert_attributes(attrs[i], names, item) + end + end + end + + def assert_textinput10(attrs, textinput) + _wrap_assertion do + names = %w(about title description name link) + assert_attributes(attrs, names, textinput) + end + end + + + def assert_channel09(attrs, channel) + _wrap_assertion do + n_attrs = normalized_attrs(attrs) + + names = %w(title description link language rating + copyright pubDate lastBuildDate docs + managingEditor webMaster) + assert_attributes(attrs, names, channel) + + %w(skipHours skipDays).each do |name| + value = n_attrs[name] + if value + target = channel.__send__(name) + __send__("assert_channel09_#{name}", value, target) + end + end + end + end + + def assert_channel09_skipDays(contents, skipDays) + _wrap_assertion do + days = skipDays.days + contents.each_with_index do |content, i| + assert_equal(content, days[i].content) + end + end + end + + def assert_channel09_skipHours(contents, skipHours) + _wrap_assertion do + hours = skipHours.hours + contents.each_with_index do |content, i| + assert_equal(content.to_i, hours[i].content) + end + end + end + + def assert_image09(attrs, image) + _wrap_assertion do + names = %w(url link title description) + names << ["width", :integer] + names << ["height", :integer] + assert_attributes(attrs, names, image) + end + end + + def assert_items09(attrs, items) + _wrap_assertion do + names = %w(title link description) + items.each_with_index do |item, i| + assert_attributes(attrs[i], names, item) + end + end + end + def assert_textinput09(attrs, textinput) + _wrap_assertion do + names = %w(title description name link) + assert_attributes(attrs, names, textinput) + end + end + + + def assert_channel20(attrs, channel) + _wrap_assertion do + n_attrs = normalized_attrs(attrs) + + names = %w(title link description language copyright + managingEditor webMaster pubDate + lastBuildDate generator docs rating) + names << ["ttl", :integer] + assert_attributes(attrs, names, channel) + + %w(cloud categories skipHours skipDays).each do |name| + value = n_attrs[name] + if value + target = channel.__send__(name) + __send__("assert_channel20_#{name}", value, target) + end + end + end + end + + def assert_channel20_skipDays(contents, skipDays) + assert_channel09_skipDays(contents, skipDays) + end + + def assert_channel20_skipHours(contents, skipHours) + assert_channel09_skipHours(contents, skipHours) + end + + def assert_channel20_cloud(attrs, cloud) + _wrap_assertion do + names = %w(domain path registerProcedure protocol) + names << ["port", :integer] + assert_attributes(attrs, names, cloud) + end + end + + def assert_channel20_categories(attrs, categories) + _wrap_assertion do + names = %w(domain content) + categories.each_with_index do |category, i| + assert_attributes(attrs[i], names, category) + end + end + end + + def assert_image20(attrs, image) + _wrap_assertion do + names = %w(url link title description) + names << ["width", :integer] + names << ["height", :integer] + assert_attributes(attrs, names, image) + end + end + + def assert_items20(attrs, items) + _wrap_assertion do + names = %w(about title link description) + items.each_with_index do |item, i| + assert_attributes(attrs[i], names, item) + + n_attrs = normalized_attrs(attrs[i]) + + %w(source enclosure categories guid).each do |name| + value = n_attrs[name] + if value + target = item.__send__(name) + __send__("assert_items20_#{name}", value, target) + end + end + end + end + end + + def assert_items20_source(attrs, source) + _wrap_assertion do + assert_attributes(attrs, %w(url content), source) + end + end + + def assert_items20_enclosure(attrs, enclosure) + _wrap_assertion do + names = ["url", ["length", :integer], "type"] + assert_attributes(attrs, names, enclosure) + end + end + + def assert_items20_categories(attrs, categories) + _wrap_assertion do + assert_channel20_categories(attrs, categories) + end + end + + def assert_items20_guid(attrs, guid) + _wrap_assertion do + names = [["isPermaLink", :boolean], ["content"]] + assert_attributes(attrs, names, guid) + end + end + + def assert_textinput20(attrs, textinput) + _wrap_assertion do + names = %w(title description name link) + assert_attributes(attrs, names, textinput) + end + end end end diff --git a/test/rss/test_version.rb b/test/rss/test_version.rb index 237f364b20..9b64f0d4ed 100644 --- a/test/rss/test_version.rb +++ b/test/rss/test_version.rb @@ -3,7 +3,7 @@ require "rss-testcase" module RSS class TestVersion < TestCase def test_version - assert_equal("0.1.6", ::RSS::VERSION) + assert_equal("0.2.0", ::RSS::VERSION) end end end diff --git a/test/rss/test_xml-stylesheet.rb b/test/rss/test_xml-stylesheet.rb index c88a858f56..b946af1294 100644 --- a/test/rss/test_xml-stylesheet.rb +++ b/test/rss/test_xml-stylesheet.rb @@ -14,7 +14,7 @@ module RSS {:media => "print", :title => "FOO"}, {:charset => "UTF-8", :alternate => "yes"}, ].each do |attrs| - assert_xml_stylesheet_attrs(attrs, XMLStyleSheet.new(*attrs)) + assert_xml_stylesheet_attrs(attrs, XMLStyleSheet.new(attrs)) end end @@ -35,8 +35,8 @@ module RSS :media => "printer", :charset => "UTF-8", :alternate => "yes"}, ].each do |attrs| - target, contents = parse_pi(XMLStyleSheet.new(*attrs).to_s) - assert_xml_stylesheet(target, attrs, XMLStyleSheet.new(*contents)) + target, contents = parse_pi(XMLStyleSheet.new(attrs).to_s) + assert_xml_stylesheet(target, attrs, XMLStyleSheet.new(contents)) end end @@ -1,7 +1,7 @@ #define RUBY_VERSION "1.8.6" -#define RUBY_RELEASE_DATE "2007-10-15" +#define RUBY_RELEASE_DATE "2007-10-21" #define RUBY_VERSION_CODE 186 -#define RUBY_RELEASE_CODE 20071015 +#define RUBY_RELEASE_CODE 20071021 #define RUBY_PATCHLEVEL 5000 #define RUBY_VERSION_MAJOR 1 @@ -9,7 +9,7 @@ #define RUBY_VERSION_TEENY 6 #define RUBY_RELEASE_YEAR 2007 #define RUBY_RELEASE_MONTH 10 -#define RUBY_RELEASE_DAY 15 +#define RUBY_RELEASE_DAY 21 #ifdef RUBY_EXTERN RUBY_EXTERN const char ruby_version[]; |