diff options
Diffstat (limited to 'lib/rss')
42 files changed, 1065 insertions, 340 deletions
diff --git a/lib/rss/0.9.rb b/lib/rss/0.9.rb index 7b24e7596d..d852a6a85e 100644 --- a/lib/rss/0.9.rb +++ b/lib/rss/0.9.rb @@ -1,14 +1,48 @@ +# frozen_string_literal: false require "rss/parser" module RSS + ## + # = RSS 0.9 support + # + # RSS has three different versions. This module contains support for version + # 0.9.1[http://www.rssboard.org/rss-0-9-1-netscape]. + # + # == Producing RSS 0.9 + # + # Producing our own RSS feeds is easy as well. Let's make a very basic feed: + # + # require "rss" + # + # rss = RSS::Maker.make("0.91") do |maker| + # maker.channel.language = "en" + # maker.channel.author = "matz" + # maker.channel.updated = Time.now.to_s + # maker.channel.link = "http://www.ruby-lang.org/en/feeds/news.rss" + # maker.channel.title = "Example Feed" + # maker.channel.description = "A longer description of my feed." + # maker.image.url = "http://www.ruby-lang.org/images/logo.gif" + # maker.image.title = "An image" + # maker.items.new_item do |item| + # item.link = "http://www.ruby-lang.org/en/news/2010/12/25/ruby-1-9-2-p136-is-released/" + # item.title = "Ruby 1.9.2-p136 is released" + # item.updated = Time.now.to_s + # end + # end + # + # puts rss + # + # As you can see, this is a very Builder-like DSL. This code will spit out an + # RSS 0.9 feed with one item. If we needed a second item, we'd make another + # block with maker.items.new_item and build a second one. module RSS09 NSPOOL = {} ELEMENTS = [] def self.append_features(klass) super - + klass.install_must_call_validator('', "") end end @@ -123,7 +157,7 @@ module RSS def not_need_to_call_setup_maker_variables %w(image textInput) end - + class SkipDays < Element include RSS09 @@ -146,11 +180,11 @@ module RSS self.content = args[0] end end - + end - + end - + class SkipHours < Element include RSS09 @@ -174,13 +208,13 @@ module RSS end end end - + end - + class Image < Element include RSS09 - + %w(url title link).each do |name| install_text_element(name, "", nil) end @@ -239,9 +273,9 @@ module RSS end end end - + class Item < Element - + include RSS09 [ @@ -269,7 +303,7 @@ module RSS @enclosure.setup_maker(item) if @enclosure @source.setup_maker(item) if @source end - + class Source < Element include RSS09 @@ -279,7 +313,7 @@ module RSS ].each do |name, uri, required| install_get_attribute(name, uri, required) end - + content_setup def initialize(*args) @@ -341,7 +375,7 @@ module RSS class Category < Element include RSS09 - + [ ["domain", "", false] ].each do |name, uri, required| @@ -369,11 +403,11 @@ module RSS category.domain = domain category.content = content end - + end end - + class TextInput < Element include RSS09 @@ -399,9 +433,9 @@ module RSS maker.textinput end end - + end - + end RSS09::ELEMENTS.each do |name| @@ -411,8 +445,8 @@ module RSS module ListenerMixin private def initial_start_rss(tag_name, prefix, attrs, ns) - check_ns(tag_name, prefix, ns, "") - + check_ns(tag_name, prefix, ns, "", false) + @rss = Rss.new(attrs['version'], @version, @encoding, @standalone) @rss.do_validate = @do_validate @rss.xml_stylesheets = @xml_stylesheets @@ -422,7 +456,7 @@ module RSS end @proc_stack.push(pr) end - + end end diff --git a/lib/rss/1.0.rb b/lib/rss/1.0.rb index f04e61c5eb..fb63937c5e 100644 --- a/lib/rss/1.0.rb +++ b/lib/rss/1.0.rb @@ -1,14 +1,47 @@ +# frozen_string_literal: false require "rss/parser" module RSS + ## + # = RSS 1.0 support + # + # RSS has three different versions. This module contains support for version + # 1.0[http://web.resource.org/rss/1.0/] + # + # == Producing RSS 1.0 + # + # Producing our own RSS feeds is easy as well. Let's make a very basic feed: + # + # require "rss" + # + # rss = RSS::Maker.make("1.0") do |maker| + # maker.channel.language = "en" + # maker.channel.author = "matz" + # maker.channel.about = "About my feed." + # maker.channel.updated = Time.now.to_s + # maker.channel.link = "http://www.ruby-lang.org/en/feeds/news.rss" + # maker.channel.title = "Example Feed" + # maker.channel.description = "A longer description of my feed." + # maker.items.new_item do |item| + # item.link = "http://www.ruby-lang.org/en/news/2010/12/25/ruby-1-9-2-p136-is-released/" + # item.title = "Ruby 1.9.2-p136 is released" + # item.updated = Time.now.to_s + # end + # end + # + # puts rss + # + # As you can see, this is a very Builder-like DSL. This code will spit out an + # RSS 1.0 feed with one item. If we needed a second item, we'd make another + # block with maker.items.new_item and build a second one. module RSS10 NSPOOL = {} ELEMENTS = [] def self.append_features(klass) super - + klass.install_must_call_validator('', ::RSS::URI) end @@ -64,13 +97,13 @@ module RSS URI end end - + [ ["resource", [URI, ""], true] ].each do |name, uri, required| install_get_attribute(name, uri, required) end - + def initialize(*args) if Utils.element_initialize_arguments?(args) super @@ -98,10 +131,10 @@ module RSS end @tag_name = 'Seq' - + install_have_children_element("li", URI, "*") install_must_call_validator('rdf', ::RSS::RDF::URI) - + def initialize(*args) if Utils.element_initialize_arguments?(args) super @@ -114,7 +147,7 @@ module RSS def full_name tag_name_with_prefix(PREFIX) end - + def setup_maker(target) lis.each do |li| target << li.resource @@ -135,10 +168,10 @@ module RSS end @tag_name = 'Bag' - + install_have_children_element("li", URI, "*") install_must_call_validator('rdf', URI) - + def initialize(*args) if Utils.element_initialize_arguments?(args) super @@ -151,7 +184,7 @@ module RSS def full_name tag_name_with_prefix(PREFIX) end - + def setup_maker(target) lis.each do |li| target << li.resource @@ -162,7 +195,7 @@ module RSS class Channel < Element include RSS10 - + class << self def required_uri @@ -202,17 +235,17 @@ module RSS def maker_target(maker) maker.channel end - + def setup_maker_attributes(channel) channel.about = about end class Image < Element - + include RSS10 class << self - + def required_uri ::RSS::URI end @@ -225,7 +258,7 @@ module RSS install_get_attribute(name, uri, required, nil, nil, "#{PREFIX}:#{name}") end - + def initialize(*args) if Utils.element_initialize_arguments?(args) super @@ -237,11 +270,11 @@ module RSS end class Textinput < Element - + include RSS10 class << self - + def required_uri ::RSS::URI end @@ -254,7 +287,7 @@ module RSS install_get_attribute(name, uri, required, nil, nil, "#{PREFIX}:#{name}") end - + def initialize(*args) if Utils.element_initialize_arguments?(args) super @@ -264,7 +297,7 @@ module RSS end end end - + class Items < Element include RSS10 @@ -272,16 +305,16 @@ module RSS Seq = ::RSS::RDF::Seq class << self - + def required_uri ::RSS::URI end - + end install_have_child_element("Seq", URI, nil) install_must_call_validator('rdf', URI) - + def initialize(*args) if Utils.element_initialize_arguments?(args) super @@ -309,7 +342,7 @@ module RSS include RSS10 class << self - + def required_uri ::RSS::URI end @@ -351,7 +384,7 @@ module RSS def required_uri ::RSS::URI end - + end @@ -436,7 +469,7 @@ module RSS module ListenerMixin private def initial_start_RDF(tag_name, prefix, attrs, ns) - check_ns(tag_name, prefix, ns, RDF::URI) + check_ns(tag_name, prefix, ns, RDF::URI, false) @rss = RDF.new(@version, @encoding, @standalone) @rss.do_validate = @do_validate diff --git a/lib/rss/2.0.rb b/lib/rss/2.0.rb index 3798da4eb7..13f9ade918 100644 --- a/lib/rss/2.0.rb +++ b/lib/rss/2.0.rb @@ -1,7 +1,39 @@ +# frozen_string_literal: false require "rss/0.9" module RSS + ## + # = RSS 2.0 support + # + # RSS has three different versions. This module contains support for version + # 2.0[http://www.rssboard.org/rss-specification] + # + # == Producing RSS 2.0 + # + # Producing our own RSS feeds is easy as well. Let's make a very basic feed: + # + # require "rss" + # + # rss = RSS::Maker.make("2.0") do |maker| + # maker.channel.language = "en" + # maker.channel.author = "matz" + # maker.channel.updated = Time.now.to_s + # maker.channel.link = "http://www.ruby-lang.org/en/feeds/news.rss" + # maker.channel.title = "Example Feed" + # maker.channel.description = "A longer description of my feed." + # maker.items.new_item do |item| + # item.link = "http://www.ruby-lang.org/en/news/2010/12/25/ruby-1-9-2-p136-is-released/" + # item.title = "Ruby 1.9.2-p136 is released" + # item.updated = Time.now.to_s + # end + # end + # + # puts rss + # + # As you can see, this is a very Builder-like DSL. This code will spit out an + # RSS 2.0 feed with one item. If we needed a second item, we'd make another + # block with maker.items.new_item and build a second one. class Rss class Channel @@ -29,7 +61,7 @@ module RSS Category = Item::Category class Item - + [ ["comments", "?"], ["author", "?"], @@ -57,9 +89,9 @@ module RSS _setup_maker_element(item) @guid.setup_maker(item) if @guid end - + class Guid < Element - + include RSS09 [ diff --git a/lib/rss/atom.rb b/lib/rss/atom.rb index 7cba934feb..38e927478c 100644 --- a/lib/rss/atom.rb +++ b/lib/rss/atom.rb @@ -1,9 +1,25 @@ -require 'base64' +# frozen_string_literal: false require 'rss/parser' module RSS + ## + # Atom is an XML-based document format that is used to describe 'feeds' of related information. + # A typical use is in a news feed where the information is periodically updated and which users + # can subscribe to. The Atom format is described in http://tools.ietf.org/html/rfc4287 + # + # The Atom module provides support in reading and creating feeds. + # + # See the RSS module for examples consuming and creating feeds. module Atom + + ## + # The Atom URI W3C Namespace + URI = "http://www.w3.org/2005/Atom" + + ## + # The XHTML URI W3C Namespace + XHTML_URI = "http://www.w3.org/1999/xhtml" module CommonModel @@ -21,10 +37,12 @@ module RSS end klass.class_eval do class << self + # Returns the Atom URI W3C Namespace def required_uri URI end + # Returns true def need_parent? true end @@ -75,6 +93,12 @@ module RSS end end + # The TextConstruct module is used to define a Text construct Atom element, + # which is used to store small quantities of human-readable text. + # + # The TextConstruct has a type attribute, e.g. text, html, xhtml + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#text.constructs module TextConstruct def self.append_features(klass) super @@ -101,6 +125,8 @@ module RSS end attr_writer :xhtml + + # Returns or builds the XHTML content. def xhtml return @xhtml if @xhtml.nil? if @xhtml.is_a?(XML::Element) and @@ -114,10 +140,13 @@ module RSS {"xmlns" => XHTML_URI}, children) end + # Returns true if type is "xhtml". def have_xml_content? @type == "xhtml" end + # Raises a MissingTagError or NotExpectedTagError + # if the element is not properly formatted. def atom_validate(ignore_unknown_element, tags, uri) if have_xml_content? if @xhtml.nil? @@ -141,7 +170,15 @@ module RSS end end + # The PersonConstruct module is used to define a person Atom element that can be + # used to describe a person, corporation or similar entity. + # + # The PersonConstruct has a Name, Uri and Email child elements. + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#atomPersonConstruct module PersonConstruct + + # Adds attributes for name, uri, and email to the +klass+ def self.append_features(klass) super klass.class_eval do @@ -159,22 +196,36 @@ module RSS target.__send__("new_#{self.class.name.split(/::/).last.downcase}") end + # The name of the person or entity. + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.name class Name < RSS::Element include CommonModel include ContentModel end + # The URI of the person or entity. + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.uri class Uri < RSS::Element include CommonModel include URIContentModel end + # The email of the person or entity. + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.email class Email < RSS::Element include CommonModel include ContentModel end end + # Element used to describe an Atom date and time in the ISO 8601 format + # + # Examples: + # * 2013-03-04T15:30:02Z + # * 2013-03-04T10:30:02-05:00 module DateConstruct def self.append_features(klass) super @@ -184,12 +235,17 @@ module RSS end end + # Raises NotAvailableValueError if element content is nil def atom_validate(ignore_unknown_element, tags, uri) raise NotAvailableValueError.new(tag_name, "") if content.nil? end end module DuplicateLinkChecker + # Checks if there are duplicate links with the same type and hreflang attributes + # that have an alternate (or empty) rel attribute + # + # Raises a TooMuchTagError if there are duplicates found def validate_duplicate_links(links) link_infos = {} links.each do |link| @@ -204,6 +260,28 @@ module RSS end end + # Defines the top-level element of an Atom Feed Document. + # It consists of a number of children Entry elements, + # and has the following attributes: + # + # * author + # * categories + # * category + # * content + # * contributor + # * entries (aliased as items) + # * entry + # * generator + # * icon + # * id + # * link + # * logo + # * rights + # * subtitle + # * title + # * updated + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.feed class Feed < RSS::Element include RootElementMixin include CommonModel @@ -231,6 +309,7 @@ module RSS tag, URI, occurs, tag, *args) end + # Creates a new Atom feed def initialize(version=nil, encoding=nil, standalone=nil) super("1.0", version, encoding, standalone) @feed_type = "atom" @@ -239,6 +318,8 @@ module RSS alias_method :items, :entries + # Returns true if there are any authors for the feed or any of the Entry + # child elements have an author def have_author? authors.any? {|author| !author.to_s.empty?} or entries.any? {|entry| entry.have_author?(false)} @@ -275,11 +356,23 @@ module RSS end end + # PersonConstruct that contains information regarding the author + # of a Feed or Entry. + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.author class Author < RSS::Element include CommonModel include PersonConstruct end + # Contains information about a category associated with a Feed or Entry. + # It has the following attributes: + # + # * term + # * scheme + # * label + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.category class Category < RSS::Element include CommonModel @@ -297,11 +390,18 @@ module RSS end end + # PersonConstruct that contains information regarding the + # contributors of a Feed or Entry. + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.contributor class Contributor < RSS::Element include CommonModel include PersonConstruct end + # Contains information on the agent used to generate the feed. + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.generator class Generator < RSS::Element include CommonModel include ContentModel @@ -322,16 +422,34 @@ module RSS end end + # Defines an image that provides a visual identification for a eed. + # The image should have an aspect ratio of 1:1. + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.icon class Icon < RSS::Element include CommonModel include URIContentModel end + # Defines the Universally Unique Identifier (UUID) for a Feed or Entry. + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.id class Id < RSS::Element include CommonModel include URIContentModel end + # Defines a reference to a Web resource. It has the following + # attributes: + # + # * href + # * rel + # * type + # * hreflang + # * title + # * length + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.link class Link < RSS::Element include CommonModel @@ -352,6 +470,10 @@ module RSS end end + # Defines an image that provides a visual identification for the Feed. + # The image should have an aspect ratio of 2:1 (horizontal:vertical). + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.logo class Logo < RSS::Element include CommonModel include URIContentModel @@ -366,26 +488,60 @@ module RSS end end + # TextConstruct that contains copyright information regarding + # the content in an Entry or Feed. It should not be used to + # convey machine readable licensing information. + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.rights class Rights < RSS::Element include CommonModel include TextConstruct end + # TextConstruct that conveys a description or subtitle for a Feed. + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.subtitle class Subtitle < RSS::Element include CommonModel include TextConstruct end + # TextConstruct that conveys a description or title for a Feed or Entry. + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.title class Title < RSS::Element include CommonModel include TextConstruct end + # DateConstruct indicating the most recent time when a Feed or + # Entry was modified in a way the publisher considers + # significant. + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.updated class Updated < RSS::Element include CommonModel include DateConstruct end + # Defines a child Atom Entry element of an Atom Feed element. + # It has the following attributes: + # + # * author + # * category + # * categories + # * content + # * contributor + # * id + # * link + # * published + # * rights + # * source + # * summary + # * title + # * updated + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.entry class Entry < RSS::Element include CommonModel include DuplicateLinkChecker @@ -409,6 +565,12 @@ module RSS tag, URI, occurs, tag, *args) end + # Returns whether any of the following are true: + # + # * There are any authors in the feed + # * If the parent element has an author and the +check_parent+ + # parameter was given. + # * There is a source element that has an author def have_author?(check_parent=true) authors.any? {|author| !author.to_s.empty?} or (check_parent and @parent and @parent.have_author?) or @@ -435,9 +597,18 @@ module RSS items.new_item end + # Feed::Author Author = Feed::Author + # Feed::Category Category = Feed::Category + # Contains or links to the content of the Entry. + # It has the following attributes: + # + # * type + # * src + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.content class Content < RSS::Element include CommonModel @@ -461,11 +632,15 @@ module RSS content_setup add_need_initialize_variable("xml") + # Returns the element content in XML. attr_writer :xml + + # Returns true if the element has inline XML content. def have_xml_content? inline_xhtml? or inline_other_xml? end + # Returns or builds the element content in XML. def xml return @xml unless inline_xhtml? return @xml if @xml.nil? @@ -480,6 +655,7 @@ module RSS {"xmlns" => XHTML_URI}, children) end + # Returns the element content in XHTML. def xhtml if inline_xhtml? xml @@ -488,6 +664,9 @@ module RSS end end + # Raises a MissingAttributeError, NotAvailableValueError, + # MissingTagError or NotExpectedTagError if the element is + # not properly formatted. def atom_validate(ignore_unknown_element, tags, uri) if out_of_line? raise MissingAttributeError.new(tag_name, "type") if @type.nil? @@ -504,19 +683,27 @@ module RSS end end + # Returns true if the element contains inline content + # that has a text or HTML media type, or no media type at all. def inline_text? !out_of_line? and [nil, "text", "html"].include?(@type) end + # Returns true if the element contains inline content that + # has a HTML media type. def inline_html? return false if out_of_line? @type == "html" or mime_split == ["text", "html"] end + # Returns true if the element contains inline content that + # has a XHTML media type. def inline_xhtml? !out_of_line? and @type == "xhtml" end + # Returns true if the element contains inline content that + # has a MIME media type. def inline_other? return false if out_of_line? media_type, subtype = mime_split @@ -524,15 +711,19 @@ module RSS true end + # Returns true if the element contains inline content that + # has a text media type. def inline_other_text? return false unless inline_other? return false if inline_other_xml? - media_type, subtype = mime_split + media_type, = mime_split return true if "text" == media_type.downcase false end + # Returns true if the element contains inline content that + # has a XML media type. def inline_other_xml? return false unless inline_other? @@ -547,14 +738,18 @@ module RSS false end + # Returns true if the element contains inline content + # encoded in base64. def inline_other_base64? inline_other? and !inline_other_text? and !inline_other_xml? end + # Returns true if the element contains linked content. def out_of_line? not @src.nil? end + # Splits the type attribute into an array, e.g. ["text", "xml"] def mime_split media_type = subtype = nil if /\A\s*([a-z]+)\/([a-z\+]+)\s*(?:;.*)?\z/i =~ @type.to_s @@ -564,6 +759,7 @@ module RSS [media_type, subtype] end + # Returns true if the content needs to be encoded in base64. def need_base64_encode? inline_other_base64? end @@ -574,17 +770,43 @@ module RSS end end + # Feed::Contributor Contributor = Feed::Contributor + # Feed::Id Id = Feed::Id + # Feed::Link Link = Feed::Link + # DateConstruct that usually indicates the time of the initial + # creation of an Entry. + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.published class Published < RSS::Element include CommonModel include DateConstruct end + # Feed::Rights Rights = Feed::Rights + # Defines a Atom Source element. It has the following attributes: + # + # * author + # * category + # * categories + # * content + # * contributor + # * generator + # * icon + # * id + # * link + # * logo + # * rights + # * subtitle + # * title + # * updated + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.source class Source < RSS::Element include CommonModel @@ -607,34 +829,71 @@ module RSS tag, URI, occurs, tag, *args) end + # Returns true if the Source element has an author. def have_author? !author.to_s.empty? end + # Feed::Author Author = Feed::Author + # Feed::Category Category = Feed::Category + # Feed::Contributor Contributor = Feed::Contributor + # Feed::Generator Generator = Feed::Generator + # Feed::Icon Icon = Feed::Icon + # Feed::Id Id = Feed::Id + # Feed::Link Link = Feed::Link + # Feed::Logo Logo = Feed::Logo + # Feed::Rights Rights = Feed::Rights + # Feed::Subtitle Subtitle = Feed::Subtitle + # Feed::Title Title = Feed::Title + # Feed::Updated Updated = Feed::Updated end + # TextConstruct that describes a summary of the Entry. + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.summary class Summary < RSS::Element include CommonModel include TextConstruct end + # Feed::Title Title = Feed::Title + # Feed::Updated Updated = Feed::Updated end end + # Defines a top-level Atom Entry element, + # used as the document element of a stand-alone Atom Entry Document. + # It has the following attributes: + # + # * author + # * category + # * categories + # * content + # * contributor + # * id + # * link + # * published + # * rights + # * source + # * summary + # * title + # * updated + # + # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.entry] class Entry < RSS::Element include RootElementMixin include CommonModel @@ -659,21 +918,26 @@ module RSS tag, URI, occurs, tag, *args) end + # Creates a new Atom Entry element. def initialize(version=nil, encoding=nil, standalone=nil) super("1.0", version, encoding, standalone) @feed_type = "atom" @feed_subtype = "entry" end + # Returns the Entry in an array. def items [self] end + # Sets up the +maker+ for constructing Entry elements. def setup_maker(maker) maker = maker.maker if maker.respond_to?("maker") super(maker) end + # Returns where there are any authors present or there is a + # source with an author. def have_author? authors.any? {|author| !author.to_s.empty?} or (source and source.have_author?) @@ -695,17 +959,29 @@ module RSS maker.items.new_item end + # Feed::Entry::Author Author = Feed::Entry::Author + # Feed::Entry::Category Category = Feed::Entry::Category + # Feed::Entry::Content Content = Feed::Entry::Content + # Feed::Entry::Contributor Contributor = Feed::Entry::Contributor + # Feed::Entry::Id Id = Feed::Entry::Id + # Feed::Entry::Link Link = Feed::Entry::Link + # Feed::Entry::Published Published = Feed::Entry::Published + # Feed::Entry::Rights Rights = Feed::Entry::Rights + # Feed::Entry::Source Source = Feed::Entry::Source + # Feed::Entry::Summary Summary = Feed::Entry::Summary + # Feed::Entry::Title Title = Feed::Entry::Title + # Feed::Entry::Updated Updated = Feed::Entry::Updated end end @@ -717,7 +993,7 @@ module RSS module ListenerMixin private def initial_start_feed(tag_name, prefix, attrs, ns) - check_ns(tag_name, prefix, ns, Atom::URI) + check_ns(tag_name, prefix, ns, Atom::URI, false) @rss = Atom::Feed.new(@version, @encoding, @standalone) @rss.do_validate = @do_validate @@ -732,7 +1008,7 @@ module RSS end def initial_start_entry(tag_name, prefix, attrs, ns) - check_ns(tag_name, prefix, ns, Atom::URI) + check_ns(tag_name, prefix, ns, Atom::URI, false) @rss = Atom::Entry.new(@version, @encoding, @standalone) @rss.do_validate = @do_validate diff --git a/lib/rss/content.rb b/lib/rss/content.rb index b12ee918aa..d35311075a 100644 --- a/lib/rss/content.rb +++ b/lib/rss/content.rb @@ -1,7 +1,10 @@ +# frozen_string_literal: false require "rss/rss" module RSS + # The prefix for the Content XML namespace. CONTENT_PREFIX = 'content' + # The URI of the Content specification. CONTENT_URI = "http://purl.org/rss/1.0/modules/content/" module ContentModel diff --git a/lib/rss/content/1.0.rb b/lib/rss/content/1.0.rb index e7c0c19685..1367dfe092 100644 --- a/lib/rss/content/1.0.rb +++ b/lib/rss/content/1.0.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require 'rss/1.0' -require 'rss/content' module RSS RDF.install_ns(CONTENT_PREFIX, CONTENT_URI) diff --git a/lib/rss/content/2.0.rb b/lib/rss/content/2.0.rb index 8671b5b1a6..3b468248ac 100644 --- a/lib/rss/content/2.0.rb +++ b/lib/rss/content/2.0.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require "rss/2.0" -require "rss/content" module RSS Rss.install_ns(CONTENT_PREFIX, CONTENT_URI) diff --git a/lib/rss/converter.rb b/lib/rss/converter.rb index 415a319188..b92e35a051 100644 --- a/lib/rss/converter.rb +++ b/lib/rss/converter.rb @@ -1,12 +1,17 @@ +# frozen_string_literal: false require "rss/utils" module RSS class Converter - + include Utils def initialize(to_enc, from_enc=nil) + if "".respond_to?(:encode) + @to_encoding = to_enc + return + end normalized_to_enc = to_enc.downcase.gsub(/-/, '_') from_enc ||= 'utf-8' normalized_from_enc = from_enc.downcase.gsub(/-/, '_') @@ -23,7 +28,11 @@ module RSS end def convert(value) - value + if value.is_a?(String) and value.respond_to?(:encode) + value.encode(@to_encoding) + else + value + end end def def_convert(depth=0) @@ -55,11 +64,11 @@ module RSS raise UnknownConversionMethodError.new(to_enc, from_enc) end end - + def def_else_enc(to_enc, from_enc) def_iconv_convert(to_enc, from_enc, 0) end - + def def_same_enc() def_convert do |value| value @@ -93,40 +102,40 @@ module RSS def def_to_euc_jp_from_utf_8 def_uconv_convert_if_can('u8toeuc', 'EUC-JP', 'UTF-8', '-We') end - + def def_to_utf_8_from_euc_jp def_uconv_convert_if_can('euctou8', 'UTF-8', 'EUC-JP', '-Ew') end - + def def_to_shift_jis_from_utf_8 def_uconv_convert_if_can('u8tosjis', 'Shift_JIS', 'UTF-8', '-Ws') end - + def def_to_utf_8_from_shift_jis def_uconv_convert_if_can('sjistou8', 'UTF-8', 'Shift_JIS', '-Sw') end - + def def_to_euc_jp_from_shift_jis require "nkf" def_convert do |value| "NKF.nkf('-Se', #{value})" end end - + def def_to_shift_jis_from_euc_jp require "nkf" def_convert do |value| "NKF.nkf('-Es', #{value})" end end - + def def_to_euc_jp_from_iso_2022_jp require "nkf" def_convert do |value| "NKF.nkf('-Je', #{value})" end end - + def def_to_iso_2022_jp_from_euc_jp require "nkf" def_convert do |value| @@ -139,7 +148,7 @@ module RSS "#{value}.unpack('C*').pack('U*')" end end - + def def_to_iso_8859_1_from_utf_8 def_convert do |value| <<-EOC @@ -156,7 +165,7 @@ module RSS EOC end end - + end - + end diff --git a/lib/rss/dublincore.rb b/lib/rss/dublincore.rb index 7ba239f8f1..8d1a551947 100644 --- a/lib/rss/dublincore.rb +++ b/lib/rss/dublincore.rb @@ -1,7 +1,10 @@ +# frozen_string_literal: false require "rss/rss" module RSS + # The prefix for the Dublin Core XML namespace. DC_PREFIX = 'dc' + # The URI of the Dublin Core specification. DC_URI = "http://purl.org/dc/elements/1.1/" module BaseDublinCoreModel @@ -25,7 +28,7 @@ module RSS def #{full_name} @#{full_name}.first and @#{full_name}.first.value end - + def #{full_name}=(new_value) @#{full_name}[0] = Utils.new_with_value_if_need(#{klass_name}, new_value) end @@ -50,7 +53,7 @@ module RSS EOC end end - + module DublinCoreModel extend BaseModel @@ -76,26 +79,26 @@ module RSS DATE_ELEMENTS = { "date" => "w3cdtf", } - + ELEMENT_NAME_INFOS = DublinCoreModel::TEXT_ELEMENTS.to_a DublinCoreModel::DATE_ELEMENTS.each do |name, | ELEMENT_NAME_INFOS << [name, nil] end - + ELEMENTS = TEXT_ELEMENTS.keys + DATE_ELEMENTS.keys ELEMENTS.each do |name, plural_name| module_eval(<<-EOC, *get_file_and_line_from_caller(0)) class DublinCore#{Utils.to_class_name(name)} < Element include RSS10 - + content_setup class << self def required_prefix DC_PREFIX end - + def required_uri DC_URI end @@ -105,7 +108,7 @@ module RSS alias_method(:value, :content) alias_method(:value=, :content=) - + def initialize(*args) if Utils.element_initialize_arguments?(args) super @@ -114,7 +117,7 @@ module RSS self.content = args[0] end end - + def full_name tag_name_with_prefix(DC_PREFIX) end diff --git a/lib/rss/dublincore/1.0.rb b/lib/rss/dublincore/1.0.rb index e193c6d2c2..1d96fab9b9 100644 --- a/lib/rss/dublincore/1.0.rb +++ b/lib/rss/dublincore/1.0.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require "rss/1.0" -require "rss/dublincore" module RSS RDF.install_ns(DC_PREFIX, DC_URI) diff --git a/lib/rss/dublincore/2.0.rb b/lib/rss/dublincore/2.0.rb index 82ed1888c5..e3011fef6a 100644 --- a/lib/rss/dublincore/2.0.rb +++ b/lib/rss/dublincore/2.0.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require "rss/2.0" -require "rss/dublincore" module RSS Rss.install_ns(DC_PREFIX, DC_URI) diff --git a/lib/rss/dublincore/atom.rb b/lib/rss/dublincore/atom.rb index e78df4821b..0b8b11e440 100644 --- a/lib/rss/dublincore/atom.rb +++ b/lib/rss/dublincore/atom.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require "rss/atom" -require "rss/dublincore" module RSS module Atom diff --git a/lib/rss/image.rb b/lib/rss/image.rb index c4714aea12..6b86ec0e5b 100644 --- a/lib/rss/image.rb +++ b/lib/rss/image.rb @@ -1,13 +1,18 @@ +# frozen_string_literal: false require 'rss/1.0' require 'rss/dublincore' module RSS + # The prefix for the Image XML namespace. IMAGE_PREFIX = 'image' + # The URI for the Image specification. IMAGE_URI = 'http://purl.org/rss/1.0/modules/image/' RDF.install_ns(IMAGE_PREFIX, IMAGE_URI) + # This constant holds strings which contain the names of + # image elements, with the appropriate prefix. IMAGE_ELEMENTS = [] %w(item favicon).each do |name| @@ -15,7 +20,7 @@ module RSS BaseListener.install_class_name(IMAGE_URI, name, "Image#{class_name}") IMAGE_ELEMENTS << "#{IMAGE_PREFIX}_#{name}" end - + module ImageModelUtils def validate_one_tag_name(ignore_unknown_element, name, tags) if !ignore_unknown_element @@ -25,7 +30,7 @@ module RSS raise TooMuchTagError.new(name, tag_name) if tags.size > 1 end end - + module ImageItemModel include ImageModelUtils extend BaseModel @@ -43,12 +48,12 @@ module RSS include DublinCoreModel @tag_name = "item" - + class << self def required_prefix IMAGE_PREFIX end - + def required_uri IMAGE_URI end @@ -102,11 +107,11 @@ module RSS end end end - + module ImageFaviconModel include ImageModelUtils extend BaseModel - + def self.append_features(klass) super @@ -122,12 +127,12 @@ module RSS include DublinCoreModel @tag_name = "favicon" - + class << self def required_prefix IMAGE_PREFIX end - + def required_uri IMAGE_URI end @@ -154,7 +159,7 @@ module RSS end set_size(new_value) end - + alias image_size= size= alias image_size size diff --git a/lib/rss/itunes.rb b/lib/rss/itunes.rb index f95ca7aa2e..987b090f21 100644 --- a/lib/rss/itunes.rb +++ b/lib/rss/itunes.rb @@ -1,7 +1,10 @@ +# frozen_string_literal: false require 'rss/2.0' module RSS + # The prefix for the iTunes XML namespace. ITUNES_PREFIX = 'itunes' + # The URI of the iTunes specification. ITUNES_URI = 'http://www.itunes.com/dtds/podcast-1.0.dtd' Rss.install_ns(ITUNES_PREFIX, ITUNES_URI) @@ -48,7 +51,7 @@ module RSS ELEMENT_INFOS = [["author"], ["block", :yes_other], - ["explicit", :yes_clean_other], + ["explicit", :explicit_clean_other], ["keywords", :csv], ["subtitle"], ["summary"]] diff --git a/lib/rss/maker.rb b/lib/rss/maker.rb index bcba1aaff3..33d285f6af 100644 --- a/lib/rss/maker.rb +++ b/lib/rss/maker.rb @@ -1,30 +1,65 @@ +# frozen_string_literal: false require "rss/rss" module RSS + ## + # + # Provides a set of builders for various RSS objects + # + # * Feeds + # * RSS 0.91 + # * RSS 1.0 + # * RSS 2.0 + # * Atom 1.0 + # + # * Elements + # * Atom::Entry + module Maker + + # Collection of supported makers MAKERS = {} class << self + # Builder for an RSS object + # Creates an object of the type passed in +args+ + # + # Executes the +block+ to populate elements of the created RSS object def make(version, &block) - m = maker(version) - raise UnsupportedMakerVersionError.new(version) if m.nil? - m[:maker].make(m[:version], &block) + self[version].make(&block) end - def maker(version) - MAKERS[version] + # Returns the maker for the +version+ + def [](version) + maker_info = maker(version) + raise UnsupportedMakerVersionError.new(version) if maker_info.nil? + maker_info[:maker] end + # Adds a maker to the set of supported makers def add_maker(version, normalized_version, maker) MAKERS[version] = {:maker => maker, :version => normalized_version} end + # Returns collection of supported maker versions def versions MAKERS.keys.uniq.sort end + # Returns collection of supported makers def makers - MAKERS.values.collect {|info| info[:maker]}.uniq + MAKERS.values.collect { |info| info[:maker] }.uniq + end + + # Returns true if the version is supported + def supported?(version) + versions.include?(version) + end + + private + # Can I remove this method? + def maker(version) + MAKERS[version] end end end diff --git a/lib/rss/maker/0.9.rb b/lib/rss/maker/0.9.rb index 72b14dc977..622a4c30b4 100644 --- a/lib/rss/maker/0.9.rb +++ b/lib/rss/maker/0.9.rb @@ -1,17 +1,18 @@ +# frozen_string_literal: false require "rss/0.9" require "rss/maker/base" module RSS module Maker - + class RSS09 < RSSBase - - def initialize(feed_version="0.92") + + def initialize(feed_version) super @feed_type = "rss" end - + private def make_feed Rss.new(@feed_version, @version, @encoding, @standalone) @@ -24,7 +25,7 @@ module RSS class Channel < ChannelBase def to_feed(rss) channel = Rss::Channel.new - set = setup_values(channel) + setup_values(channel) _not_set_required_variables = not_set_required_variables if _not_set_required_variables.empty? rss.channel = channel @@ -38,20 +39,20 @@ module RSS raise NotSetError.new("maker.channel", _not_set_required_variables) end end - + private def setup_items(rss) @maker.items.to_feed(rss) end - + def setup_image(rss) @maker.image.to_feed(rss) end - + def setup_textinput(rss) @maker.textinput.to_feed(rss) end - + def variables super + ["pubDate"] end @@ -78,7 +79,7 @@ module RSS end end end - + class Day < DayBase def to_feed(rss, days) day = Rss::Channel::SkipDays::Day.new @@ -96,7 +97,7 @@ module RSS end end end - + class SkipHours < SkipHoursBase def to_feed(rss, channel) unless @hours.empty? @@ -108,7 +109,7 @@ module RSS end end end - + class Hour < HourBase def to_feed(rss, hours) hour = Rss::Channel::SkipHours::Hour.new @@ -126,7 +127,7 @@ module RSS end end end - + class Cloud < CloudBase def to_feed(*args) end @@ -243,7 +244,7 @@ module RSS true end end - + class Items < ItemsBase def to_feed(rss) if rss.channel @@ -253,11 +254,11 @@ module RSS setup_other_elements(rss, rss.items) end end - + class Item < ItemBase def to_feed(rss) item = Rss::Channel::Item.new - set = setup_values(item) + setup_values(item) _not_set_required_variables = not_set_required_variables if _not_set_required_variables.empty? rss.items << item @@ -439,7 +440,7 @@ module RSS end end end - + class Textinput < TextinputBase def to_feed(rss) textInput = Rss::Channel::TextInput.new @@ -457,11 +458,52 @@ module RSS end end end - - add_maker("0.9", "0.92", RSS09) - add_maker("0.91", "0.91", RSS09) - add_maker("0.92", "0.92", RSS09) - add_maker("rss0.91", "0.91", RSS09) - add_maker("rss0.92", "0.92", RSS09) + + class RSS091 < RSS09 + def initialize(feed_version="0.91") + super + end + + class Channel < RSS09::Channel + end + + class Items < RSS09::Items + class Item < RSS09::Items::Item + end + end + + class Image < RSS09::Image + end + + class Textinput < RSS09::Textinput + end + end + + class RSS092 < RSS09 + def initialize(feed_version="0.92") + super + end + + class Channel < RSS09::Channel + end + + class Items < RSS09::Items + class Item < RSS09::Items::Item + end + end + + class Image < RSS09::Image + end + + class Textinput < RSS09::Textinput + end + end + + add_maker("0.9", "0.92", RSS092) + add_maker("0.91", "0.91", RSS091) + add_maker("0.92", "0.92", RSS092) + add_maker("rss0.9", "0.92", RSS092) + add_maker("rss0.91", "0.91", RSS091) + add_maker("rss0.92", "0.92", RSS092) end end diff --git a/lib/rss/maker/1.0.rb b/lib/rss/maker/1.0.rb index a1e2594f70..3aee77e913 100644 --- a/lib/rss/maker/1.0.rb +++ b/lib/rss/maker/1.0.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require "rss/1.0" require "rss/maker/base" @@ -25,13 +26,14 @@ module RSS end class Channel < ChannelBase + include SetupDefaultLanguage 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) + setup_values(channel) channel.dc_dates.clear rss.channel = channel set_parent(channel, rss) @@ -60,7 +62,7 @@ module RSS rss.channel.items = items set_parent(rss.channel, items) end - + def setup_image(rss) if @maker.image.have_required_values? image = RDF::Channel::Image.new(@maker.image.url) @@ -91,11 +93,11 @@ module RSS class SkipDays < SkipDaysBase def to_feed(*args) end - + class Day < DayBase end end - + class SkipHours < SkipHoursBase def to_feed(*args) end @@ -103,7 +105,7 @@ module RSS class Hour < HourBase end end - + class Cloud < CloudBase def to_feed(*args) end @@ -403,7 +405,7 @@ module RSS end end end - + class Textinput < TextinputBase def to_feed(rss) if @link diff --git a/lib/rss/maker/2.0.rb b/lib/rss/maker/2.0.rb index 67d68126ac..1f77a014d1 100644 --- a/lib/rss/maker/2.0.rb +++ b/lib/rss/maker/2.0.rb @@ -1,12 +1,13 @@ +# frozen_string_literal: false require "rss/2.0" require "rss/maker/0.9" module RSS module Maker - + class RSS20 < RSS09 - + def initialize(feed_version="2.0") super end @@ -17,17 +18,17 @@ module RSS def required_variable_names %w(link) end - + class SkipDays < RSS09::Channel::SkipDays class Day < RSS09::Channel::SkipDays::Day end end - + class SkipHours < RSS09::Channel::SkipHours class Hour < RSS09::Channel::SkipHours::Hour end end - + class Cloud < RSS09::Channel::Cloud def to_feed(rss, channel) cloud = Rss::Channel::Cloud.new @@ -51,7 +52,7 @@ module RSS category.to_feed(rss, channel) end end - + class Category < RSS09::Channel::Categories::Category def to_feed(rss, channel) category = Rss::Channel::Category.new @@ -81,14 +82,14 @@ module RSS end end end - + class Image < RSS09::Image private def required_element? false end end - + class Items < RSS09::Items class Item < RSS09::Items::Item private @@ -179,7 +180,7 @@ module RSS category.to_feed(rss, item) end end - + class Category < RSS09::Items::Item::Categories::Category def to_feed(rss, item) category = Rss::Channel::Item::Category.new @@ -212,11 +213,11 @@ module RSS end end end - + class Textinput < RSS09::Textinput end end - + add_maker("2.0", "2.0", RSS20) add_maker("rss2.0", "2.0", RSS20) end diff --git a/lib/rss/maker/atom.rb b/lib/rss/maker/atom.rb index fd3198cd9e..e0cd7623c8 100644 --- a/lib/rss/maker/atom.rb +++ b/lib/rss/maker/atom.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require "rss/atom" require "rss/maker/base" diff --git a/lib/rss/maker/base.rb b/lib/rss/maker/base.rb index 56bf04657e..bc4ca84141 100644 --- a/lib/rss/maker/base.rb +++ b/lib/rss/maker/base.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'forwardable' require 'rss/rss' @@ -23,15 +24,17 @@ module RSS end def inherited(subclass) - subclass.const_set("OTHER_ELEMENTS", []) - subclass.const_set("NEED_INITIALIZE_VARIABLES", []) + 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") + def add_need_initialize_variable(variable_name, init_value=nil, + &init_block) + init_value ||= init_block self::NEED_INITIALIZE_VARIABLES << [variable_name, init_value] end @@ -45,7 +48,7 @@ module RSS def_delegators("@#{plural}", :push, :pop, :shift, :unshift) def_delegators("@#{plural}", :each, :size, :empty?, :clear) - add_need_initialize_variable(plural, "[]") + add_need_initialize_variable(plural) {[]} module_eval(<<-EOC, __FILE__, __LINE__ + 1) def new_#{name} @@ -74,7 +77,9 @@ module RSS 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}") + add_need_initialize_variable(name) do |object| + object.send("make_#{name}") + end module_eval(<<-EOC, __FILE__, __LINE__ + 1) private def setup_#{name}(feed, current) @@ -185,7 +190,19 @@ module RSS private def initialize_variables self.class.need_initialize_variables.each do |variable_name, init_value| - instance_eval("@#{variable_name} = #{init_value}", __FILE__, __LINE__) + if init_value.nil? + value = nil + else + if init_value.respond_to?(:call) + value = init_value.call(self) + elsif init_value.is_a?(String) + # just for backward compatibility + value = instance_eval(init_value, __FILE__, __LINE__) + else + value = init_value + end + end + instance_variable_set("@#{variable_name}", value) end end @@ -222,7 +239,7 @@ module RSS setter = "#{var}=" if target.respond_to?(setter) value = __send__(var) - if value + unless value.nil? target.__send__(setter, value) set = true end @@ -238,7 +255,8 @@ module RSS def variables self.class.need_initialize_variables.find_all do |name, init| - "nil" == init + # init == "nil" is just for backward compatibility + init.nil? or init == "nil" end.collect do |name, init| name end @@ -336,36 +354,62 @@ module RSS module SetupDefaultDate private - def _set_default_values(&block) + def _set_default_values keep = { :date => date, :dc_dates => dc_dates.to_a.dup, } - _date = date + _date = _parse_date_if_needed(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) + super ensure - date = keep[:date] + self.date = keep[:date] dc_dates.replace(keep[:dc_dates]) end + + def _parse_date_if_needed(date_value) + date_value = Time.parse(date_value) if date_value.is_a?(String) + date_value + end + end + + module SetupDefaultLanguage + private + def _set_default_values + keep = { + :dc_languages => dc_languages.to_a.dup, + } + _language = language + if _language and + !dc_languages.any? {|dc_language| dc_language.value == _language} + dc_language = self.class::DublinCoreLanguages::DublinCoreLanguage.new(self) + dc_language.value = _language.dup + dc_languages.unshift(dc_language) + end + super + ensure + dc_languages.replace(keep[:dc_languages]) + end end class RSSBase < Base class << self - def make(version, &block) - new(version).make(&block) + def make(*args, &block) + new(*args).make(&block) end end %w(xml_stylesheets channel image items textinput).each do |element| attr_reader element - add_need_initialize_variable(element, "make_#{element}") - module_eval(<<-EOC, __FILE__, __LINE__) + add_need_initialize_variable(element) do |object| + object.send("make_#{element}") + end + module_eval(<<-EOC, __FILE__, __LINE__ + 1) private def setup_#{element}(feed) @#{element}.to_feed(feed) @@ -376,7 +420,7 @@ module RSS end EOC end - + attr_reader :feed_version alias_method(:rss_version, :feed_version) attr_accessor :version, :encoding, :standalone @@ -390,14 +434,10 @@ module RSS @encoding = "UTF-8" @standalone = nil end - + def make - if block_given? - yield(self) - to_feed - else - nil - end + yield(self) + to_feed end def to_feed @@ -405,13 +445,10 @@ module RSS setup_xml_stylesheets(feed) setup_elements(feed) setup_other_elements(feed) - if feed.valid? - feed - else - nil - end + feed.validate + feed end - + private remove_method :make_xml_stylesheets def make_xml_stylesheets @@ -428,7 +465,7 @@ module RSS attr_accessor attribute add_need_initialize_variable(attribute) end - + def to_feed(feed) xss = ::RSS::XMLStyleSheet.new guess_type_if_need(xss) @@ -451,7 +488,7 @@ module RSS end end end - + class ChannelBase < Base include SetupDefaultDate @@ -472,12 +509,24 @@ module RSS end %w(id about language - managingEditor webMaster rating docs date - lastBuildDate ttl).each do |element| + managingEditor webMaster rating docs ttl).each do |element| attr_accessor element add_need_initialize_variable(element) end + %w(date lastBuildDate).each do |date_element| + attr_reader date_element + add_need_initialize_variable(date_element) + end + + def date=(_date) + @date = _parse_date_if_needed(_date) + end + + def lastBuildDate=(_date) + @lastBuildDate = _parse_date_if_needed(_date) + end + def pubDate date end @@ -526,7 +575,7 @@ module RSS end end end - + class SkipHoursBase < Base def_array_element("hour") @@ -537,7 +586,7 @@ module RSS end end end - + class CloudBase < Base %w(domain port path registerProcedure protocol).each do |element| attr_accessor element @@ -607,7 +656,7 @@ module RSS include AtomTextConstructBase end end - + class ImageBase < Base %w(title url width height description).each do |element| attr_accessor element @@ -618,18 +667,18 @@ module RSS @maker.channel.link end end - + class ItemsBase < Base def_array_element("item") attr_accessor :do_sort, :max_size - + def initialize(maker) super @do_sort = false @max_size = -1 end - + def normalize if @max_size >= 0 sort_if_need[0...@max_size] @@ -670,13 +719,22 @@ module RSS ["contributor", "name"], ].each do |name, attribute| def_classed_elements(name, attribute) - end + end - %w(date comments id published).each do |element| + %w(comments id published).each do |element| attr_accessor element add_need_initialize_variable(element) end + %w(date).each do |date_element| + attr_reader date_element + add_need_initialize_variable(date_element) + end + + def date=(_date) + @date = _parse_date_if_needed(_date) + end + def pubDate date end @@ -715,6 +773,14 @@ module RSS attr_accessor element add_need_initialize_variable(element) end + + def permanent_link? + isPermaLink + end + + def permanent_link=(bool) + self.isPermaLink = bool + end end class EnclosureBase < Base @@ -725,6 +791,8 @@ module RSS end class SourceBase < Base + include SetupDefaultDate + %w(authors categories contributors generator icon logo rights subtitle title).each do |name| def_classed_element(name) @@ -736,7 +804,7 @@ module RSS def_classed_elements(name, attribute) end - %w(id content date).each do |element| + %w(id content).each do |element| attr_accessor element add_need_initialize_variable(element) end @@ -744,6 +812,15 @@ module RSS alias_method(:url, :link) alias_method(:url=, :link=) + %w(date).each do |date_element| + attr_reader date_element + add_need_initialize_variable(date_element) + end + + def date=(_date) + @date = _parse_date_if_needed(_date) + end + def updated date end diff --git a/lib/rss/maker/content.rb b/lib/rss/maker/content.rb index 46c4911f73..3559a45ad0 100644 --- a/lib/rss/maker/content.rb +++ b/lib/rss/maker/content.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'rss/content' require 'rss/maker/1.0' require 'rss/maker/2.0' diff --git a/lib/rss/maker/dublincore.rb b/lib/rss/maker/dublincore.rb index ff4813fe19..988209c045 100644 --- a/lib/rss/maker/dublincore.rb +++ b/lib/rss/maker/dublincore.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'rss/dublincore' require 'rss/maker/1.0' @@ -11,10 +12,7 @@ module RSS plural_name ||= "#{name}s" full_name = "#{RSS::DC_PREFIX}_#{name}" full_plural_name = "#{RSS::DC_PREFIX}_#{plural_name}" - klass_name = Utils.to_class_name(name) plural_klass_name = "DublinCore#{Utils.to_class_name(plural_name)}" - full_plural_klass_name = "self.class::#{plural_klass_name}" - full_klass_name = "#{full_plural_klass_name}::#{klass_name}" klass.def_classed_elements(full_name, "value", plural_klass_name, full_plural_name, name) klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1) @@ -90,7 +88,7 @@ EOC class ChannelBase include DublinCoreModel end - + class ImageBase; include DublinCoreModel; end class ItemsBase class ItemBase diff --git a/lib/rss/maker/entry.rb b/lib/rss/maker/entry.rb index edaa31ec06..f806cbcaae 100644 --- a/lib/rss/maker/entry.rb +++ b/lib/rss/maker/entry.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require "rss/maker/atom" require "rss/maker/feed" @@ -99,7 +100,7 @@ module RSS end end - def _set_default_values(&block) + def _set_default_values keep = { :authors => authors.to_a.dup, :contributors => contributors.to_a.dup, @@ -126,7 +127,7 @@ module RSS @maker.channel.title {|t| @title = t} end self.updated ||= @maker.channel.updated - super(&block) + super ensure authors.replace(keep[:authors]) contributors.replace(keep[:contributors]) @@ -135,7 +136,7 @@ module RSS self.id = keep[:id] @rights = keep[:rights] @title = keep[:title] - self.updated = keep[:prev_updated] + self.updated = keep[:updated] end Guid = Feed::Items::Item::Guid diff --git a/lib/rss/maker/feed.rb b/lib/rss/maker/feed.rb index 3a30ad4287..fdef7ad643 100644 --- a/lib/rss/maker/feed.rb +++ b/lib/rss/maker/feed.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require "rss/maker/atom" module RSS @@ -22,6 +23,8 @@ module RSS end class Channel < ChannelBase + include SetupDefaultLanguage + def to_feed(feed) set_default_values do setup_values(feed) @@ -71,14 +74,11 @@ module RSS 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 @@ -182,6 +182,7 @@ module RSS set_default_values do entry = feed.class::Entry.new set = setup_values(entry) + entry.dc_dates.clear setup_other_elements(feed, entry) if set feed.entries << entry @@ -216,14 +217,11 @@ module RSS 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 diff --git a/lib/rss/maker/image.rb b/lib/rss/maker/image.rb index b95cf4c714..1957ba8689 100644 --- a/lib/rss/maker/image.rb +++ b/lib/rss/maker/image.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'rss/image' require 'rss/maker/1.0' require 'rss/maker/dublincore' @@ -13,7 +14,7 @@ module RSS end def self.install_image_item(klass) - klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1) + klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1) class ImageItem < ImageItemBase DublinCoreModel.install_dublin_core(self) end @@ -57,7 +58,7 @@ EOC end def self.install_image_favicon(klass) - klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1) + klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1) class ImageFavicon < ImageFaviconBase DublinCoreModel.install_dublin_core(self) end @@ -89,7 +90,7 @@ EOC end class ChannelBase; include Maker::ImageFaviconModel; end - + class ItemsBase class ItemBase; include Maker::ImageItemModel; end end diff --git a/lib/rss/maker/itunes.rb b/lib/rss/maker/itunes.rb index 7c5049129d..d964a4d942 100644 --- a/lib/rss/maker/itunes.rb +++ b/lib/rss/maker/itunes.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'rss/itunes' require 'rss/maker/2.0' @@ -12,8 +13,8 @@ module RSS 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 :explicit_clean_other + def_explicit_clean_other_accessor(klass, full_name) when :csv def_csv_accessor(klass, full_name) when :element, :attribute @@ -42,11 +43,11 @@ module RSS EOC end - def def_yes_clean_other_accessor(klass, full_name) + def def_explicit_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}) + Utils::ExplicitCleanOther.parse(#{full_name}) end EOC end @@ -176,7 +177,7 @@ module RSS %w(hour minute second).each do |name| attr_reader(name) - add_need_initialize_variable(name, '0') + add_need_initialize_variable(name, 0) end def content=(content) diff --git a/lib/rss/maker/slash.rb b/lib/rss/maker/slash.rb index 27adef3832..3bd82d3057 100644 --- a/lib/rss/maker/slash.rb +++ b/lib/rss/maker/slash.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'rss/slash' require 'rss/maker/1.0' diff --git a/lib/rss/maker/syndication.rb b/lib/rss/maker/syndication.rb index b81230457c..840b70229a 100644 --- a/lib/rss/maker/syndication.rb +++ b/lib/rss/maker/syndication.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'rss/syndication' require 'rss/maker/1.0' diff --git a/lib/rss/maker/taxonomy.rb b/lib/rss/maker/taxonomy.rb index 211603840f..76a2d1600d 100644 --- a/lib/rss/maker/taxonomy.rb +++ b/lib/rss/maker/taxonomy.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'rss/taxonomy' require 'rss/maker/1.0' require 'rss/maker/dublincore' @@ -72,12 +73,12 @@ EOC class TaxonomyTopicBase < Base include DublinCoreModel include TaxonomyTopicsModel - + attr_accessor :value add_need_initialize_variable("value") alias_method(:taxo_link, :value) alias_method(:taxo_link=, :value=) - + def have_required_values? @value end @@ -88,11 +89,11 @@ EOC class RSSBase include TaxonomyTopicModel end - + class ChannelBase include TaxonomyTopicsModel end - + class ItemsBase class ItemBase include TaxonomyTopicsModel diff --git a/lib/rss/maker/trackback.rb b/lib/rss/maker/trackback.rb index 278fe53ebe..f97691c608 100644 --- a/lib/rss/maker/trackback.rb +++ b/lib/rss/maker/trackback.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'rss/trackback' require 'rss/maker/1.0' require 'rss/maker/2.0' @@ -19,7 +20,7 @@ module RSS class TrackBackAboutBase < Base attr_accessor :value add_need_initialize_variable("value") - + alias_method(:resource, :value) alias_method(:resource=, :value=) alias_method(:content, :value) diff --git a/lib/rss/parser.rb b/lib/rss/parser.rb index 9e4919223c..a9842e6d40 100644 --- a/lib/rss/parser.rb +++ b/lib/rss/parser.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require "forwardable" require "open-uri" @@ -34,8 +35,8 @@ module RSS class NotValidXMLParser < Error def initialize(parser) super("#{parser} is not an available XML parser. " << - "Available XML parser"<< - (AVAILABLE_PARSERS.size > 1 ? "s are ": " is ") << + "Available XML parser" << + (AVAILABLE_PARSERS.size > 1 ? "s are " : " is ") << "#{AVAILABLE_PARSERS.inspect}.") end end @@ -98,7 +99,7 @@ module RSS return rss if maybe_xml?(rss) uri = to_uri(rss) - + if uri.respond_to?(:read) uri.read elsif !rss.tainted? and File.readable?(rss) @@ -113,7 +114,7 @@ module RSS source.is_a?(String) and /</ =~ source end - # Attempt to convert rss to a URI, but just return it if + # Attempt to convert rss to a URI, but just return it if # there's a ::URI::Error def to_uri(rss) return rss if rss.is_a?(::URI::Generic) @@ -133,7 +134,7 @@ module RSS listener.raise_for_undefined_entity? end end - + def initialize(rss) @listener = self.class.listener.new @rss = rss @@ -196,13 +197,13 @@ module RSS def available_tags(uri) (@@accessor_bases[uri] || {}).keys end - + # register uri against this name. def register_uri(uri, name) @@registered_uris[name] ||= {} @@registered_uris[name][uri] = nil end - + # test if this uri is registered against this name def uri_registered?(uri, name) @@registered_uris[name].has_key?(uri) @@ -220,9 +221,7 @@ module RSS name = (@@class_names[uri] || {})[tag_name] return name if name - tag_name = tag_name.gsub(/[_\-]([a-z]?)/) do - $1.upcase - end + tag_name = tag_name.gsub(/[_\-]([a-z]?)/) {$1.upcase} tag_name[0, 1].upcase + tag_name[1..-1] end @@ -230,11 +229,11 @@ module RSS install_accessor_base(uri, name, accessor_base) def_get_text_element(uri, name, *get_file_and_line_from_caller(1)) end - + def raise_for_undefined_entity? true end - + private # set the accessor for the uri, tag_name pair def install_accessor_base(uri, tag_name, accessor_base) @@ -281,7 +280,7 @@ module RSS @xml_element = nil @last_xml_element = nil end - + # set instance vars for version, encoding, standalone def xmldecl(version, encoding, standalone) @version, @encoding, @standalone = version, encoding, standalone @@ -389,14 +388,12 @@ 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 class_name and - (current_class.const_defined?(class_name) or - current_class.constants.include?(class_name)) + if known_class?(current_class, class_name) next_class = current_class.const_get(class_name) start_have_something_element(local, prefix, attrs, ns, next_class) else if !@do_validate or @ignore_unknown_element - @proc_stack.push(nil) + @proc_stack.push(setup_next_element_in_unknown_element) else parent = "ROOT ELEMENT???" if current_class.tag_name @@ -407,19 +404,42 @@ module RSS end end - NAMESPLIT = /^(?:([\w:][-\w\d.]*):)?([\w:][-\w\d.]*)/ + if Module.method(:const_defined?).arity == -1 + def known_class?(target_class, class_name) + class_name and + (target_class.const_defined?(class_name, false) or + target_class.constants.include?(class_name.to_sym)) + end + else + def known_class?(target_class, class_name) + class_name and + (target_class.const_defined?(class_name) or + target_class.constants.include?(class_name)) + end + end + + NAMESPLIT = /^(?:([\w:][-\w.]*):)?([\w:][-\w.]*)/ def split_name(name) name =~ NAMESPLIT [$1 || '', $2] end - def check_ns(tag_name, prefix, ns, require_uri) - unless _ns(ns, prefix) == require_uri - if @do_validate + def check_ns(tag_name, prefix, ns, require_uri, ignore_unknown_element=nil) + if _ns(ns, prefix) == require_uri + true + else + if ignore_unknown_element.nil? + ignore_unknown_element = @ignore_unknown_element + end + + if ignore_unknown_element + false + elsif @do_validate raise NSError.new(tag_name, prefix, require_uri) else # Force bind required URI with prefix @ns_stack.last[prefix] = require_uri + true end end end @@ -427,7 +447,7 @@ module RSS def start_get_text_element(tag_name, prefix, ns, required_uri) pr = Proc.new do |text, tags| setter = self.class.setter(required_uri, tag_name) - if @last_element.respond_to?(setter) + if setter and @last_element.respond_to?(setter) if @do_validate getter = self.class.getter(required_uri, tag_name) if @last_element.__send__(getter) @@ -446,9 +466,12 @@ module RSS end def start_have_something_element(tag_name, prefix, attrs, ns, klass) - check_ns(tag_name, prefix, ns, klass.required_uri) - attributes = collect_attributes(tag_name, prefix, attrs, ns, klass) - @proc_stack.push(setup_next_element(tag_name, klass, attributes)) + if check_ns(tag_name, prefix, ns, klass.required_uri) + attributes = collect_attributes(tag_name, prefix, attrs, ns, klass) + @proc_stack.push(setup_next_element(tag_name, klass, attributes)) + else + @proc_stack.push(setup_next_element_in_unknown_element) + end end def collect_attributes(tag_name, prefix, attrs, ns, klass) @@ -504,7 +527,7 @@ module RSS else if klass.have_content? if @last_element.need_base64_encode? - text = Base64.decode64(text.lstrip) + text = text.lstrip.unpack("m").first end @last_element.content = text end @@ -515,9 +538,15 @@ module RSS @last_element = previous end end + + def setup_next_element_in_unknown_element + current_element, @last_element = @last_element, nil + Proc.new {@last_element = current_element} + end end unless const_defined? :AVAILABLE_PARSER_LIBRARIES + # The list of all available libraries for parsing. AVAILABLE_PARSER_LIBRARIES = [ ["rss/xmlparser", :XMLParserParser], ["rss/xmlscanner", :XMLScanParser], @@ -525,6 +554,7 @@ module RSS ] end + # The list of all available parsers, in constant form. AVAILABLE_PARSERS = [] AVAILABLE_PARSER_LIBRARIES.each do |lib, parser| diff --git a/lib/rss/rexmlparser.rb b/lib/rss/rexmlparser.rb index 4dabf59199..ef0595e447 100644 --- a/lib/rss/rexmlparser.rb +++ b/lib/rss/rexmlparser.rb @@ -1,13 +1,9 @@ +# frozen_string_literal: false require "rexml/document" require "rexml/streamlistener" -/\A(\d+)\.(\d+)(?:\.\d+)+\z/ =~ REXML::Version -if ([$1.to_i, $2.to_i] <=> [2, 5]) < 0 - raise LoadError, "needs REXML 2.5 or later (#{REXML::Version})" -end - module RSS - + class REXMLParser < BaseParser class << self @@ -15,7 +11,7 @@ module RSS REXMLListener end end - + private def _parse begin @@ -28,9 +24,9 @@ module RSS raise NotWellFormedError.new(line){e.message} end end - + end - + class REXMLListener < BaseListener include REXML::StreamListener @@ -41,7 +37,7 @@ module RSS false end end - + def xmldecl(version, encoding, standalone) super(version, encoding, standalone == "yes") # Encoding is converted to UTF-8 when REXML parse XML. diff --git a/lib/rss/rss.rb b/lib/rss/rss.rb index 5aa4a2cedd..db87e11ad5 100644 --- a/lib/rss/rss.rb +++ b/lib/rss/rss.rb @@ -1,8 +1,17 @@ +# frozen_string_literal: false require "time" class Time class << self unless respond_to?(:w3cdtf) + # This method converts a W3CDTF string date/time format to Time object. + # + # The W3CDTF format is defined here: http://www.w3.org/TR/NOTE-datetime + # + # Time.w3cdtf('2003-02-15T13:50:05-05:00') + # # => 2003-02-15 10:50:05 -0800 + # Time.w3cdtf('2003-02-15T13:50:05-05:00').class + # # => Time def w3cdtf(date) if /\A\s* (-?\d+)-(\d\d)-(\d\d) @@ -20,7 +29,7 @@ class Time datetime = apply_offset(*(datetime + [off])) datetime << usec time = Time.utc(*datetime) - time.localtime unless zone_utc?(zone) + force_zone!(time, zone, off) time else datetime << usec @@ -34,17 +43,25 @@ class Time end unless method_defined?(:w3cdtf) + # This method converts a Time object to a String. The String contains the + # time in W3CDTF date/time format. + # + # The W3CDTF format is defined here: http://www.w3.org/TR/NOTE-datetime + # + # Time.now.w3cdtf + # # => "2013-08-26T14:12:10.817124-07:00" def w3cdtf if usec.zero? fraction_digits = 0 else - fraction_digits = Math.log10(usec.to_s.sub(/0*$/, '').to_i).floor + 1 + fraction_digits = strftime('%6N').index(/0*\z/) end xmlschema(fraction_digits) end end end + require "English" require "rss/utils" require "rss/converter" @@ -52,14 +69,20 @@ require "rss/xml-stylesheet" module RSS - VERSION = "0.2.4" + # The current version of RSS + VERSION = "0.2.7" + # The URI of the RSS 1.0 specification URI = "http://purl.org/rss/1.0/" - DEBUG = false + DEBUG = false # :nodoc: + # The basic error all other RSS errors stem from. Rescue this error if you + # want to handle any given RSS error and you don't care about the details. class Error < StandardError; end + # RSS, being an XML-based format, has namespace support. If two namespaces are + # declared with the same name, an OverlappedPrefixError will be raised. class OverlappedPrefixError < Error attr_reader :prefix def initialize(prefix) @@ -67,8 +90,13 @@ module RSS end end + # The InvalidRSSError error is the base class for a variety of errors + # related to a poorly-formed RSS feed. Rescue this error if you only + # care that a file could be invalid, but don't care how it is invalid. class InvalidRSSError < Error; end + # Since RSS is based on XML, it must have opening and closing tags that + # match. If they don't, a MissingTagError will be raised. class MissingTagError < InvalidRSSError attr_reader :tag, :parent def initialize(tag, parent) @@ -77,6 +105,9 @@ module RSS end end + # Some tags must only exist a specific number of times in a given RSS feed. + # If a feed has too many occurrences of one of these tags, a TooMuchTagError + # will be raised. class TooMuchTagError < InvalidRSSError attr_reader :tag, :parent def initialize(tag, parent) @@ -85,6 +116,8 @@ module RSS end end + # Certain attributes are required on specific tags in an RSS feed. If a feed + # is missing one of these attributes, a MissingAttributeError is raised. class MissingAttributeError < InvalidRSSError attr_reader :tag, :attribute def initialize(tag, attribute) @@ -93,6 +126,8 @@ module RSS end end + # RSS does not allow for free-form tag names, so if an RSS feed contains a + # tag that we don't know about, an UnknownTagError is raised. class UnknownTagError < InvalidRSSError attr_reader :tag, :uri def initialize(tag, uri) @@ -101,6 +136,7 @@ module RSS end end + # Raised when an unexpected tag is encountered. class NotExpectedTagError < InvalidRSSError attr_reader :tag, :uri, :parent def initialize(tag, uri, parent) @@ -109,8 +145,10 @@ module RSS end end # For backward compatibility :X - NotExceptedTagError = NotExpectedTagError + NotExceptedTagError = NotExpectedTagError # :nodoc: + # Attributes are in key-value form, and if there's no value provided for an + # attribute, a NotAvailableValueError will be raised. class NotAvailableValueError < InvalidRSSError attr_reader :tag, :value, :attribute def initialize(tag, value, attribute=nil) @@ -122,6 +160,7 @@ module RSS end end + # Raised when an unknown conversion error occurs. class UnknownConversionMethodError < Error attr_reader :to, :from def initialize(to, from) @@ -131,8 +170,9 @@ module RSS end end # for backward compatibility - UnknownConvertMethod = UnknownConversionMethodError + UnknownConvertMethod = UnknownConversionMethodError # :nodoc: + # Raised when a conversion failure occurs. class ConversionError < Error attr_reader :string, :to, :from def initialize(string, to, from) @@ -143,6 +183,7 @@ module RSS end end + # Raised when a required variable is not set. class NotSetError < Error attr_reader :name, :variables def initialize(name, variables) @@ -152,6 +193,7 @@ module RSS end end + # Raised when a RSS::Maker attempts to use an unknown maker. class UnsupportedMakerVersionError < Error attr_reader :version def initialize(version) @@ -228,7 +270,7 @@ EOC else rv << value end - rv << "</#{elem_name}>" + rv << "</#{elem_name}>" rv else '' @@ -248,7 +290,7 @@ EOC # accessor convert_attr_reader name date_writer(name, type, disp_name) - + install_element(name) do |n, elem_name| <<-EOC if @#{n} @@ -259,7 +301,7 @@ EOC else rv << value end - rv << "</#{elem_name}>" + rv << "</#{elem_name}>" rv else '' @@ -284,7 +326,6 @@ EOC 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}) @@ -305,7 +346,6 @@ EOC 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}) @@ -326,7 +366,6 @@ EOC 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}) @@ -335,13 +374,12 @@ EOC end end - def yes_clean_other_attr_reader(*attrs) + def explicit_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}) + ExplicitCleanOther.parse(@#{attr}) end EOC end @@ -349,7 +387,6 @@ EOC 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}? @@ -367,7 +404,6 @@ EOC 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 @@ -508,7 +544,7 @@ EOC EOC end - def yes_clean_other_writer(name, disp_name=name) + def explicit_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) @@ -560,11 +596,10 @@ EOC def #{accessor_name}=(*args) receiver = self.class.name - warn("Warning:\#{caller.first.sub(/:in `.*'\z/, '')}: " \ - "Don't use `\#{receiver}\##{accessor_name} = XXX'/" \ + warn("Don't use `\#{receiver}\##{accessor_name} = XXX'/" \ "`\#{receiver}\#set_#{accessor_name}(XXX)'. " \ "Those APIs are not sense of Ruby. " \ - "Use `\#{receiver}\##{plural_name} << XXX' instead of them.") + "Use `\#{receiver}\##{plural_name} << XXX' instead of them.", uplevel: 1) if args.size == 1 @#{accessor_name}.push(args[0]) else @@ -633,7 +668,7 @@ EOC include SetupMaker INDENT = " " - + MUST_CALL_VALIDATORS = {} MODELS = [] GET_ATTRIBUTES = [] @@ -670,18 +705,18 @@ EOC end def inherited(klass) - klass.const_set("MUST_CALL_VALIDATORS", {}) - klass.const_set("MODELS", []) - klass.const_set("GET_ATTRIBUTES", []) - klass.const_set("HAVE_CHILDREN_ELEMENTS", []) - klass.const_set("TO_ELEMENT_METHODS", []) - klass.const_set("NEED_INITIALIZE_VARIABLES", []) - klass.const_set("PLURAL_FORMS", {}) + klass.const_set(:MUST_CALL_VALIDATORS, {}) + klass.const_set(:MODELS, []) + klass.const_set(:GET_ATTRIBUTES, []) + klass.const_set(:HAVE_CHILDREN_ELEMENTS, []) + klass.const_set(:TO_ELEMENT_METHODS, []) + klass.const_set(:NEED_INITIALIZE_VARIABLES, []) + klass.const_set(:PLURAL_FORMS, {}) tag_name = klass.name.split(/::/).last tag_name[0, 1] = tag_name[0, 1].downcase - klass.instance_variable_set("@tag_name", tag_name) - klass.instance_variable_set("@have_content", false) + klass.instance_variable_set(:@tag_name, tag_name) + klass.instance_variable_set(:@have_content, false) end def install_must_call_validator(prefix, uri) @@ -727,8 +762,8 @@ EOC 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 :explicit_clean_other + explicit_clean_other_writer name, disp_name when :yes_other yes_other_writer name, disp_name when :csv @@ -746,8 +781,8 @@ EOC inherit_convert_attr_reader name when :uri uri_convert_attr_reader name - when :yes_clean_other - yes_clean_other_attr_reader name + when :explicit_clean_other + explicit_clean_other_attr_reader name when :yes_other yes_other_attr_reader name when :csv @@ -829,7 +864,7 @@ EOC def full_name tag_name end - + def converter=(converter) @converter = converter targets = children.dup @@ -864,7 +899,7 @@ EOC ensure @do_validate = do_validate end - + def validate_for_stream(tags, ignore_unknown_element=true) validate_attribute __validate(ignore_unknown_element, tags, false) @@ -921,7 +956,7 @@ EOC children = child children.any? {|c| c.have_required_elements?} else - !child.to_s.empty? + not child.nil? end else true @@ -984,7 +1019,7 @@ EOC end_tag = "\n#{indent}</#{full_name}>" end end - + start_tag + content.join("\n") + end_tag end @@ -1009,7 +1044,7 @@ EOC end attrs end - + def tag_name_with_prefix(prefix) "#{prefix}:#{tag_name}" end @@ -1092,9 +1127,8 @@ 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 + name, _, occurs, = model if DEBUG p "before" @@ -1200,7 +1234,7 @@ EOC __send__(self.class.xml_getter).to_s else _content = content - _content = Base64.encode64(_content) if need_base64_encode? + _content = [_content].pack("m0") if need_base64_encode? h(_content) end end @@ -1209,7 +1243,7 @@ EOC module RootElementMixin include XMLStyleSheetMixin - + attr_reader :output_encoding attr_reader :feed_type, :feed_subtype, :feed_version attr_accessor :version, :encoding, :standalone @@ -1295,7 +1329,7 @@ EOC rv << "?>\n" rv end - + def ns_declarations decls = {} self.class::NSPOOL.collect do |prefix, uri| diff --git a/lib/rss/slash.rb b/lib/rss/slash.rb index f102413b46..0055fc9f88 100644 --- a/lib/rss/slash.rb +++ b/lib/rss/slash.rb @@ -1,7 +1,10 @@ +# frozen_string_literal: false require 'rss/1.0' module RSS + # The prefix for the Slash XML namespace. SLASH_PREFIX = 'slash' + # The URI of the Slash specification. SLASH_URI = "http://purl.org/rss/1.0/modules/slash/" RDF.install_ns(SLASH_PREFIX, SLASH_URI) diff --git a/lib/rss/syndication.rb b/lib/rss/syndication.rb index 3eb15429f6..8f9620f9f3 100644 --- a/lib/rss/syndication.rb +++ b/lib/rss/syndication.rb @@ -1,18 +1,20 @@ +# frozen_string_literal: false require "rss/1.0" module RSS - + # The prefix for the Syndication XML namespace. SY_PREFIX = 'sy' + # The URI of the Syndication specification. SY_URI = "http://purl.org/rss/1.0/modules/syndication/" RDF.install_ns(SY_PREFIX, SY_URI) module SyndicationModel - + extend BaseModel - + ELEMENTS = [] - + def self.append_features(klass) super @@ -46,7 +48,7 @@ module RSS private SY_UPDATEPERIOD_AVAILABLE_VALUES = %w(hourly daily weekly monthly yearly) - def validate_sy_updatePeriod(value) + def validate_sy_updatePeriod(value) # :nodoc: unless SY_UPDATEPERIOD_AVAILABLE_VALUES.include?(value) raise NotAvailableValueError.new("updatePeriod", value) end diff --git a/lib/rss/taxonomy.rb b/lib/rss/taxonomy.rb index 276f63b05d..b7ea219e8c 100644 --- a/lib/rss/taxonomy.rb +++ b/lib/rss/taxonomy.rb @@ -1,13 +1,16 @@ +# frozen_string_literal: false require "rss/1.0" require "rss/dublincore" module RSS - + # The prefix for the Taxonomy XML namespace. TAXO_PREFIX = "taxo" + # The URI for the specification of the Taxonomy XML namespace. TAXO_URI = "http://purl.org/rss/1.0/modules/taxonomy/" RDF.install_ns(TAXO_PREFIX, TAXO_URI) + # The listing of all the taxonomy elements, with the appropriate namespace. TAXO_ELEMENTS = [] %w(link).each do |name| @@ -24,7 +27,7 @@ module RSS module TaxonomyTopicsModel extend BaseModel - + def self.append_features(klass) super @@ -37,21 +40,21 @@ module RSS class TaxonomyTopics < Element include RSS10 - + Bag = ::RSS::RDF::Bag class << self def required_prefix TAXO_PREFIX end - + def required_uri TAXO_URI end end @tag_name = "topics" - + install_have_child_element("Bag", RDF::URI, nil) install_must_call_validator('rdf', RDF::URI) @@ -84,10 +87,10 @@ module RSS end end end - + module TaxonomyTopicModel extend BaseModel - + def self.append_features(klass) super var_name = "#{TAXO_PREFIX}_topic" @@ -99,12 +102,12 @@ module RSS include DublinCoreModel include TaxonomyTopicsModel - + class << self def required_prefix TAXO_PREFIX end - + def required_uri TAXO_URI end @@ -115,7 +118,7 @@ module RSS install_get_attribute("about", ::RSS::RDF::URI, true, nil, nil, "#{RDF::PREFIX}:about") install_text_element("link", TAXO_URI, "?", "#{TAXO_PREFIX}_link") - + def initialize(*args) if Utils.element_initialize_arguments?(args) super diff --git a/lib/rss/trackback.rb b/lib/rss/trackback.rb index ee2491f332..1a3c3849b5 100644 --- a/lib/rss/trackback.rb +++ b/lib/rss/trackback.rb @@ -1,7 +1,10 @@ +# frozen_string_literal: false +# This file contains the implementation of trackbacks. It is entirely internal +# and not useful to outside developers. require 'rss/1.0' require 'rss/2.0' -module RSS +module RSS # :nodoc: all TRACKBACK_PREFIX = 'trackback' TRACKBACK_URI = 'http://madskills.com/public/xml/rss/module/trackback/' @@ -23,7 +26,7 @@ module RSS module BaseTrackBackModel ELEMENTS = %w(ping about) - + def append_features(klass) super @@ -47,7 +50,7 @@ module RSS end EOC end - + [%w(about s)].each do |name, postfix| var_name = "#{TRACKBACK_PREFIX}_#{name}" klass_name = "TrackBack#{Utils.to_class_name(name)}" @@ -105,7 +108,7 @@ module RSS def required_prefix TRACKBACK_PREFIX end - + def required_uri TRACKBACK_URI end @@ -141,17 +144,17 @@ module RSS include RSS10 class << self - + def required_prefix TRACKBACK_PREFIX end - + def required_uri TRACKBACK_URI end end - + @tag_name = "about" [ @@ -163,7 +166,7 @@ module RSS alias_method(:value, :resource) alias_method(:value=, :resource=) - + def initialize(*args) if Utils.element_initialize_arguments?(args) super @@ -185,7 +188,7 @@ module RSS def setup_maker_attributes(about) about.resource = self.resource end - + end end @@ -197,7 +200,7 @@ module RSS include RSS09 @tag_name = "ping" - + content_setup class << self @@ -205,13 +208,13 @@ module RSS def required_prefix TRACKBACK_PREFIX end - + def required_uri TRACKBACK_URI end end - + alias_method(:value, :content) alias_method(:value=, :content=) @@ -223,26 +226,26 @@ module RSS self.content = args[0] end end - + def full_name tag_name_with_prefix(TRACKBACK_PREFIX) end - + end class TrackBackAbout < Element include RSS09 @tag_name = "about" - + content_setup class << self - + def required_prefix TRACKBACK_PREFIX end - + def required_uri TRACKBACK_URI end @@ -260,11 +263,11 @@ module RSS self.content = args[0] end end - + def full_name tag_name_with_prefix(TRACKBACK_PREFIX) end - + end end diff --git a/lib/rss/utils.rb b/lib/rss/utils.rb index 0e4001e1f3..9203df9a9b 100644 --- a/lib/rss/utils.rb +++ b/lib/rss/utils.rb @@ -1,14 +1,85 @@ +# frozen_string_literal: false module RSS + + ## + # RSS::Utils is a module that holds various utility functions that are used + # across many parts of the rest of the RSS library. Like most modules named + # some variant of 'util', its methods are probably not particularly useful + # to those who aren't developing the library itself. module Utils module_function - # Convert a name_with_underscores to CamelCase. + # Given a +name+ in a name_with_underscores or a name-with-dashes format, + # returns the CamelCase version of +name+. + # + # If the +name+ is already CamelCased, nothing happens. + # + # Examples: + # + # require 'rss/utils' + # + # RSS::Utils.to_class_name("sample_name") + # # => "SampleName" + # RSS::Utils.to_class_name("with-dashes") + # # => "WithDashes" + # RSS::Utils.to_class_name("CamelCase") + # # => "CamelCase" def to_class_name(name) name.split(/[_\-]/).collect do |part| "#{part[0, 1].upcase}#{part[1..-1]}" end.join("") end - + + # Returns an array of two elements: the filename where the calling method + # is located, and the line number where it is defined. + # + # Takes an optional argument +i+, which specifies how many callers up the + # stack to look. + # + # Examples: + # + # require 'rss/utils' + # + # def foo + # p RSS::Utils.get_file_and_line_from_caller + # p RSS::Utils.get_file_and_line_from_caller(1) + # end + # + # def bar + # foo + # end + # + # def baz + # bar + # end + # + # baz + # # => ["test.rb", 5] + # # => ["test.rb", 9] + # + # If +i+ is not given, or is the default value of 0, it attempts to figure + # out the correct value. This is useful when in combination with + # instance_eval. For example: + # + # require 'rss/utils' + # + # def foo + # p RSS::Utils.get_file_and_line_from_caller(1) + # end + # + # def bar + # foo + # end + # + # instance_eval <<-RUBY, *RSS::Utils.get_file_and_line_from_caller + # def baz + # bar + # end + # RUBY + # + # baz + # + # # => ["test.rb", 8] def get_file_and_line_from_caller(i=0) file, line, = caller[i].split(':') line = line.to_i @@ -16,12 +87,24 @@ module RSS [file, line] end - # escape '&', '"', '<' and '>' for use in HTML. + # Takes a string +s+ with some HTML in it, and escapes '&', '"', '<' and '>', by + # replacing them with the appropriate entities. + # + # This method is also aliased to h, for convenience. + # + # Examples: + # + # require 'rss/utils' + # + # RSS::Utils.html_escape("Dungeons & Dragons") + # # => "Dungeons & Dragons" + # RSS::Utils.h(">_>") + # # => ">_>" def html_escape(s) s.to_s.gsub(/&/, "&").gsub(/\"/, """).gsub(/>/, ">").gsub(/</, "<") end alias h html_escape - + # If +value+ is an instance of class +klass+, return it, else # create a new instance of +klass+ with value +value+. def new_with_value_if_need(klass, value) @@ -32,20 +115,26 @@ module RSS end end + # This method is used inside of several different objects to determine + # if special behavior is needed in the constructor. + # + # Special behavior is needed if the array passed in as +args+ has + # +true+ or +false+ as its value, and if the second element of +args+ + # is a hash. def element_initialize_arguments?(args) [true, false].include?(args[0]) and args[1].is_a?(Hash) end - module YesCleanOther + module ExplicitCleanOther module_function def parse(value) if [true, false, nil].include?(value) value else case value.to_s - when /\Ayes\z/i + when /\Aexplicit|yes|true\z/i true - when /\Aclean\z/i + when /\Aclean|no|false\z/i false else nil diff --git a/lib/rss/xml-stylesheet.rb b/lib/rss/xml-stylesheet.rb index 559d6bcd56..be9cfaaf64 100644 --- a/lib/rss/xml-stylesheet.rb +++ b/lib/rss/xml-stylesheet.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require "rss/utils" module RSS @@ -8,7 +9,7 @@ module RSS super @xml_stylesheets = [] end - + private def xml_stylesheet_pi xsss = @xml_stylesheets.collect do |xss| @@ -94,7 +95,7 @@ module RSS xss.__send__("#{attr}=", __send__(attr)) end end - + private def guess_type(filename) /\.([^.]+)$/ =~ filename diff --git a/lib/rss/xml.rb b/lib/rss/xml.rb index 1ae878b772..cda8668044 100644 --- a/lib/rss/xml.rb +++ b/lib/rss/xml.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require "rss/utils" module RSS diff --git a/lib/rss/xmlparser.rb b/lib/rss/xmlparser.rb index 3dfe7d461a..cb2dd2afdd 100644 --- a/lib/rss/xmlparser.rb +++ b/lib/rss/xmlparser.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false begin require "xml/parser" rescue LoadError @@ -20,15 +21,16 @@ end module XML class Parser unless defined?(Error) - Error = ::XMLParserError + # This error is legacy, so we just set it to the new one + Error = ::XMLParserError # :nodoc: end end end module RSS - + class REXMLLikeXMLParser < ::XML::Parser - + include ::XML::Encoding_ja def listener=(listener) @@ -38,7 +40,7 @@ module RSS def startElement(name, attrs) @listener.tag_start(name, attrs) end - + def endElement(name) @listener.tag_end(name) end @@ -64,7 +66,7 @@ module RSS XMLParserListener end end - + private def _parse begin @@ -75,13 +77,13 @@ module RSS raise NotWellFormedError.new(parser.line){e.message} end end - + end - + class XMLParserListener < BaseListener include ListenerMixin - + def xmldecl(version, encoding, standalone) super # Encoding is converted to UTF-8 when XMLParser parses XML. diff --git a/lib/rss/xmlscanner.rb b/lib/rss/xmlscanner.rb index 61b9fa6bf4..6e3b13d2f5 100644 --- a/lib/rss/xmlscanner.rb +++ b/lib/rss/xmlscanner.rb @@ -1,16 +1,17 @@ +# frozen_string_literal: false require 'xmlscan/scanner' require 'stringio' module RSS - + class XMLScanParser < BaseParser - + class << self def listener XMLScanListener end end - + private def _parse begin @@ -26,11 +27,11 @@ module RSS raise NotWellFormedError.new(lineno){e.message} end end - + end class XMLScanListener < BaseListener - + include XMLScan::Visitor include ListenerMixin |
