summaryrefslogtreecommitdiff
path: root/lib/rss
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rss')
-rw-r--r--lib/rss/0.9.rb97
-rw-r--r--lib/rss/1.0.rb94
-rw-r--r--lib/rss/2.0.rb40
-rw-r--r--lib/rss/atom.rb1025
-rw-r--r--lib/rss/content.rb28
-rw-r--r--lib/rss/content/1.0.rb10
-rw-r--r--lib/rss/content/2.0.rb12
-rw-r--r--lib/rss/converter.rb45
-rw-r--r--lib/rss/dublincore.rb58
-rw-r--r--lib/rss/dublincore/1.0.rb13
-rw-r--r--lib/rss/dublincore/2.0.rb13
-rw-r--r--lib/rss/dublincore/atom.rb17
-rw-r--r--lib/rss/image.rb33
-rw-r--r--lib/rss/itunes.rb413
-rw-r--r--lib/rss/maker.rb60
-rw-r--r--lib/rss/maker/0.9.rb451
-rw-r--r--lib/rss/maker/1.0.rb344
-rw-r--r--lib/rss/maker/2.0.rb170
-rw-r--r--lib/rss/maker/atom.rb173
-rw-r--r--lib/rss/maker/base.rb969
-rw-r--r--lib/rss/maker/content.rb15
-rw-r--r--lib/rss/maker/dublincore.rb151
-rw-r--r--lib/rss/maker/entry.rb164
-rw-r--r--lib/rss/maker/feed.rb427
-rw-r--r--lib/rss/maker/image.rb119
-rw-r--r--lib/rss/maker/itunes.rb243
-rw-r--r--lib/rss/maker/slash.rb34
-rw-r--r--lib/rss/maker/syndication.rb14
-rw-r--r--lib/rss/maker/taxonomy.rb127
-rw-r--r--lib/rss/maker/trackback.rb118
-rw-r--r--lib/rss/parser.rb227
-rw-r--r--lib/rss/rexmlparser.rb16
-rw-r--r--lib/rss/rss.rb815
-rw-r--r--lib/rss/slash.rb52
-rw-r--r--lib/rss/syndication.rb21
-rw-r--r--lib/rss/taxonomy.rb25
-rw-r--r--lib/rss/trackback.rb41
-rw-r--r--lib/rss/utils.rb175
-rw-r--r--lib/rss/xml-stylesheet.rb9
-rw-r--r--lib/rss/xml.rb72
-rw-r--r--lib/rss/xmlparser.rb18
-rw-r--r--lib/rss/xmlscanner.rb11
42 files changed, 5574 insertions, 1385 deletions
diff --git a/lib/rss/0.9.rb b/lib/rss/0.9.rb
index eb2d828102..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
@@ -17,16 +51,18 @@ module RSS
include RSS09
include RootElementMixin
- include XMLStyleSheetMixin
%w(channel).each do |name|
install_have_child_element(name, "", nil)
end
- attr_accessor :rss_version, :version, :encoding, :standalone
-
- def initialize(rss_version, version=nil, encoding=nil, standalone=nil)
+ attr_writer :feed_version
+ alias_method(:rss_version, :feed_version)
+ alias_method(:rss_version=, :feed_version=)
+
+ def initialize(feed_version, version=nil, encoding=nil, standalone=nil)
super
+ @feed_type = "rss"
end
def items
@@ -58,12 +94,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
@@ -119,7 +157,7 @@ module RSS
def not_need_to_call_setup_maker_variables
%w(image textInput)
end
-
+
class SkipDays < Element
include RSS09
@@ -142,11 +180,11 @@ module RSS
self.content = args[0]
end
end
-
+
end
-
+
end
-
+
class SkipHours < Element
include RSS09
@@ -170,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
@@ -235,9 +273,9 @@ module RSS
end
end
end
-
+
class Item < Element
-
+
include RSS09
[
@@ -265,7 +303,7 @@ module RSS
@enclosure.setup_maker(item) if @enclosure
@source.setup_maker(item) if @source
end
-
+
class Source < Element
include RSS09
@@ -275,7 +313,7 @@ module RSS
].each do |name, uri, required|
install_get_attribute(name, uri, required)
end
-
+
content_setup
def initialize(*args)
@@ -337,7 +375,7 @@ module RSS
class Category < Element
include RSS09
-
+
[
["domain", "", false]
].each do |name, uri, required|
@@ -365,11 +403,11 @@ module RSS
category.domain = domain
category.content = content
end
-
+
end
end
-
+
class TextInput < Element
include RSS09
@@ -395,29 +433,30 @@ module RSS
maker.textinput
end
end
-
+
end
-
+
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)
- check_ns(tag_name, prefix, ns, "")
-
+ def initial_start_rss(tag_name, prefix, attrs, 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
@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
end
diff --git a/lib/rss/1.0.rb b/lib/rss/1.0.rb
index a945434fbf..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
@@ -45,10 +78,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
@@ -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
@@ -430,21 +463,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)
- check_ns(tag_name, prefix, ns, RDF::URI)
+ def initial_start_RDF(tag_name, prefix, attrs, ns)
+ check_ns(tag_name, prefix, ns, RDF::URI, false)
@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..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
[
@@ -105,7 +137,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..38e927478c
--- /dev/null
+++ b/lib/rss/atom.rb
@@ -0,0 +1,1025 @@
+# 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
+ 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
+ # Returns the Atom URI W3C Namespace
+ def required_uri
+ URI
+ end
+
+ # Returns true
+ 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
+
+ # 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
+ 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
+
+ # Returns or builds the XHTML content.
+ 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
+
+ # 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?
+ 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
+
+ # 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
+ [
+ ["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
+
+ # 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
+ klass.class_eval do
+ @content_type = :w3cdtf
+ include(ContentModel)
+ 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|
+ 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
+
+ # 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
+ 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
+
+ # Creates a new Atom feed
+ 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
+
+ # 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)}
+ 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
+
+ # 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
+
+ [
+ ["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
+
+ # 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
+
+ [
+ ["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
+
+ # 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
+
+ [
+ ["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
+
+ # 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
+
+ def maker_target(target)
+ target.maker.image
+ end
+
+ private
+ def setup_maker_element_writer
+ "url="
+ 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
+
+ [
+ ["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
+
+ # 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
+ (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
+
+ # 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
+
+ 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")
+
+ # 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?
+ 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
+
+ # Returns the element content in XHTML.
+ def xhtml
+ if inline_xhtml?
+ xml
+ else
+ nil
+ 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?
+ 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
+
+ # 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
+ return false if media_type.nil? or subtype.nil?
+ 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, = 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?
+
+ 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
+
+ # 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
+ media_type = $1.downcase
+ subtype = $2.downcase
+ end
+ [media_type, subtype]
+ end
+
+ # Returns true if the content needs to be encoded in base64.
+ def need_base64_encode?
+ inline_other_base64?
+ end
+
+ private
+ def empty_content?
+ out_of_line? or super
+ 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
+
+ [
+ ["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
+
+ # 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
+ 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
+
+ # 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?)
+ 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
+
+ # 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
+
+ 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, false)
+
+ @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, false)
+
+ @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..d35311075a 100644
--- a/lib/rss/content.rb
+++ b/lib/rss/content.rb
@@ -1,38 +1,34 @@
-require "rss/1.0"
+# 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/"
- RDF.install_ns(CONTENT_PREFIX, CONTENT_URI)
-
module ContentModel
-
extend BaseModel
- ELEMENTS = []
+ ELEMENTS = ["#{CONTENT_PREFIX}_encoded"]
def self.append_features(klass)
super
klass.install_must_call_validator(CONTENT_PREFIX, CONTENT_URI)
- %w(encoded).each do |name|
- klass.install_text_element(name, CONTENT_URI, "?",
- "#{CONTENT_PREFIX}_#{name}")
+ ELEMENTS.each do |full_name|
+ name = full_name[(CONTENT_PREFIX.size + 1)..-1]
+ klass.install_text_element(name, CONTENT_URI, "?", full_name)
end
end
end
- class RDF
- class Item; include ContentModel; end
- end
-
prefix_size = CONTENT_PREFIX.size + 1
- 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
+
+require 'rss/content/1.0'
+require 'rss/content/2.0'
diff --git a/lib/rss/content/1.0.rb b/lib/rss/content/1.0.rb
new file mode 100644
index 0000000000..1367dfe092
--- /dev/null
+++ b/lib/rss/content/1.0.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: false
+require 'rss/1.0'
+
+module RSS
+ RDF.install_ns(CONTENT_PREFIX, CONTENT_URI)
+
+ class RDF
+ class Item; include ContentModel; end
+ end
+end
diff --git a/lib/rss/content/2.0.rb b/lib/rss/content/2.0.rb
new file mode 100644
index 0000000000..3b468248ac
--- /dev/null
+++ b/lib/rss/content/2.0.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: false
+require "rss/2.0"
+
+module RSS
+ Rss.install_ns(CONTENT_PREFIX, CONTENT_URI)
+
+ class Rss
+ class Channel
+ class Item; include ContentModel; end
+ end
+ end
+end
diff --git a/lib/rss/converter.rb b/lib/rss/converter.rb
index d928c48223..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
@@ -80,8 +89,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
@@ -89,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|
@@ -135,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
@@ -152,7 +165,7 @@ module RSS
EOC
end
end
-
+
end
-
+
end
diff --git a/lib/rss/dublincore.rb b/lib/rss/dublincore.rb
index 8a4afd4dd9..8d1a551947 100644
--- a/lib/rss/dublincore.rb
+++ b/lib/rss/dublincore.rb
@@ -1,11 +1,11 @@
-require "rss/1.0"
+# 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/"
-
- RDF.install_ns(DC_PREFIX, DC_URI)
module BaseDublinCoreModel
def append_features(klass)
@@ -28,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
@@ -36,12 +36,24 @@ 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
-
+
module DublinCoreModel
extend BaseModel
@@ -61,32 +73,32 @@ module RSS
"language" => nil,
"relation" => nil,
"coverage" => nil,
- "rights" => "rightses" # FIXME
+ "rights" => "rights_list"
}
DATE_ELEMENTS = {
"date" => "w3cdtf",
}
-
+
ELEMENT_NAME_INFOS = DublinCoreModel::TEXT_ELEMENTS.to_a
DublinCoreModel::DATE_ELEMENTS.each do |name, |
ELEMENT_NAME_INFOS << [name, nil]
end
-
+
ELEMENTS = TEXT_ELEMENTS.keys + DATE_ELEMENTS.keys
ELEMENTS.each do |name, plural_name|
module_eval(<<-EOC, *get_file_and_line_from_caller(0))
class DublinCore#{Utils.to_class_name(name)} < Element
include RSS10
-
+
content_setup
class << self
def required_prefix
DC_PREFIX
end
-
+
def required_uri
DC_URI
end
@@ -96,7 +108,7 @@ module RSS
alias_method(:value, :content)
alias_method(:value=, :content=)
-
+
def initialize(*args)
if Utils.element_initialize_arguments?(args)
super
@@ -105,7 +117,7 @@ module RSS
self.content = args[0]
end
end
-
+
def full_name
tag_name_with_prefix(DC_PREFIX)
end
@@ -122,13 +134,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 +151,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 +158,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..1d96fab9b9
--- /dev/null
+++ b/lib/rss/dublincore/1.0.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: false
+require "rss/1.0"
+
+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..e3011fef6a
--- /dev/null
+++ b/lib/rss/dublincore/2.0.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: false
+require "rss/2.0"
+
+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..0b8b11e440
--- /dev/null
+++ b/lib/rss/dublincore/atom.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: false
+require "rss/atom"
+
+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..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'
- IMAGE_URI = 'http://web.resource.org/rss/1.0/modules/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
@@ -69,7 +74,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=
@@ -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
@@ -142,8 +147,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,9 +157,9 @@ 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=
alias image_size size
diff --git a/lib/rss/itunes.rb b/lib/rss/itunes.rb
new file mode 100644
index 0000000000..987b090f21
--- /dev/null
+++ b/lib/rss/itunes.rb
@@ -0,0 +1,413 @@
+# 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)
+
+ 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", :explicit_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..33d285f6af 100644
--- a/lib/rss/maker.rb
+++ b/lib/rss/maker.rb
@@ -1,37 +1,79 @@
+# 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)
- maker(version).make(&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
- def add_maker(version, maker)
- MAKERS[version] = maker
+ # Adds a maker to the set of supported makers
+ def add_maker(version, normalized_version, maker)
+ MAKERS[version] = {:maker => maker, :version => normalized_version}
end
- def filename_to_version(filename)
- File.basename(filename, ".*")
+ # 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
+ 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
-
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..622a4c30b4 100644
--- a/lib/rss/maker/0.9.rb
+++ b/lib/rss/maker/0.9.rb
@@ -1,19 +1,21 @@
+# frozen_string_literal: false
require "rss/0.9"
require "rss/maker/base"
module RSS
module Maker
-
+
class RSS09 < RSSBase
-
- def initialize(rss_version="0.91")
+
+ def initialize(feed_version)
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,204 +23,487 @@ module RSS
end
class Channel < ChannelBase
-
- def to_rss(rss)
+ def to_feed(rss)
channel = Rss::Channel.new
- set = setup_values(channel)
- if set
+ setup_values(channel)
+ _not_set_required_variables = not_set_required_variables
+ if _not_set_required_variables.empty?
rss.channel = channel
+ set_parent(channel, rss)
setup_items(rss)
setup_image(rss)
setup_textinput(rss)
- setup_other_elements(rss)
- 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
super + ["pubDate"]
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
+ setup_values(item)
+ _not_set_required_variables = not_set_required_variables
+ if _not_set_required_variables.empty?
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
+ []
+ end
+
+ def not_set_required_variables
+ vars = super
+ if @maker.feed_version == "0.91"
+ vars << "title" unless title {|t| t.have_required_values?}
+ vars << "link" unless link {|l| l.have_required_values?}
+ end
+ vars
end
class Guid < GuidBase
- def to_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)
+
+ 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 3e6542a007..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"
@@ -7,12 +8,13 @@ module RSS
class RSS10 < RSSBase
- def initialize
- super("1.0")
+ def initialize(feed_version="1.0")
+ super
+ @feed_type = "rss"
end
private
- def make_rss
+ def make_feed
RDF.new(@version, @encoding, @standalone)
end
@@ -24,44 +26,48 @@ module RSS
end
class Channel < ChannelBase
+ include SetupDefaultLanguage
- 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)
+ 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,136 +75,362 @@ 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
end
end
-
+
class SkipHours < SkipHoursBase
- def to_rss(*args)
+ def to_feed(*args)
end
class Hour < HourBase
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)
+ 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", "1.0", RSS10)
+ add_maker("rss1.0", "1.0", RSS10)
end
end
diff --git a/lib/rss/maker/2.0.rb b/lib/rss/maker/2.0.rb
index a958661614..1f77a014d1 100644
--- a/lib/rss/maker/2.0.rb
+++ b/lib/rss/maker/2.0.rb
@@ -1,168 +1,224 @@
+# frozen_string_literal: false
require "rss/2.0"
require "rss/maker/0.9"
module RSS
module Maker
-
+
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
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_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
+ private
+ def required_variable_names
+ []
+ end
- def have_required_values?
- @title or @description
+ def not_set_required_variables
+ vars = super
+ if !title {|t| t.have_required_values?} and
+ !description {|d| d.have_required_values?}
+ vars << "title or description"
+ end
+ vars
end
- 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", "2.0", RSS20)
+ add_maker("rss2.0", "2.0", RSS20)
end
end
diff --git a/lib/rss/maker/atom.rb b/lib/rss/maker/atom.rb
new file mode 100644
index 0000000000..e0cd7623c8
--- /dev/null
+++ b/lib/rss/maker/atom.rb
@@ -0,0 +1,173 @@
+# frozen_string_literal: false
+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..bc4ca84141 100644
--- a/lib/rss/maker/base.rb
+++ b/lib/rss/maker/base.rb
@@ -1,90 +1,237 @@
+# frozen_string_literal: false
require 'forwardable'
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
+ class << self
+ def other_elements
+ inherited_array_reader("OTHER_ELEMENTS")
+ end
+ def need_initialize_variables
+ inherited_array_reader("NEED_INITIALIZE_VARIABLES")
+ end
- klass.module_eval(<<-EOC, __FILE__, __LINE__)
+ def inherited_base
+ ::RSS::Maker::Base
+ end
- OTHER_ELEMENTS = []
- NEED_INITIALIZE_VARIABLES = []
+ def inherited(subclass)
+ subclass.const_set(:OTHER_ELEMENTS, [])
+ subclass.const_set(:NEED_INITIALIZE_VARIABLES, [])
+ end
- def self.inherited(subclass)
- subclass.const_set("OTHER_ELEMENTS", [])
- subclass.const_set("NEED_INITIALIZE_VARIABLES", [])
+ def add_other_element(variable_name)
+ self::OTHER_ELEMENTS << variable_name
+ end
+
+ def add_need_initialize_variable(variable_name, init_value=nil,
+ &init_block)
+ init_value ||= init_block
+ self::NEED_INITIALIZE_VARIABLES << [variable_name, init_value]
+ end
- 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) do |object|
+ object.send("make_#{name}")
+ end
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ private
+ def setup_#{name}(feed, current)
+ @#{name}.to_feed(feed, current)
+ end
+
+ def make_#{name}
+ self.class::#{class_name}.new(@maker)
+ end
+ EOC
+ end
+
+ def def_classed_element(name, class_name=nil, attribute_name=nil)
+ def_classed_element_without_accessor(name, class_name)
+ if attribute_name
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ def #{name}
+ if block_given?
+ yield(@#{name})
+ else
+ @#{name}.#{attribute_name}
+ end
+ end
+
+ def #{name}=(new_value)
+ @#{name}.#{attribute_name} = new_value
+ end
+ EOC
+ else
+ attr_reader name
+ end
end
- def 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|
- 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
- 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?
@@ -92,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
@@ -102,18 +249,19 @@ 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
+ # init == "nil" is just for backward compatibility
+ init.nil? or init == "nil"
end.collect do |name, init|
name
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,115 +274,207 @@ 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
+ keep = {
+ :date => date,
+ :dc_dates => dc_dates.to_a.dup,
+ }
+ _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
+ ensure
+ 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(&block)
- new.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}(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
end
-
+
def make
- if block_given?
- yield(self)
- to_rss
- else
- nil
- end
+ yield(self)
+ to_feed
end
- def to_rss
- rss = make_rss
- setup_xml_stylesheets(rss)
- setup_elements(rss)
- setup_other_elements(rss)
- if rss.channel
- rss
- else
- nil
- end
+ def to_feed
+ feed = make_feed
+ setup_xml_stylesheets(feed)
+ setup_elements(feed)
+ setup_other_elements(feed)
+ feed.validate
+ feed
end
-
+
private
remove_method :make_xml_stylesheets
def make_xml_stylesheets
XMLStyleSheets.new(self)
end
-
end
- class XMLStyleSheets
- include Base
-
- def_array_element("xml_stylesheets")
+ class XMLStyleSheets < Base
+ def_array_element("xml_stylesheet", nil, "XMLStyleSheet")
- 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 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,180 +482,203 @@ EOC
@type = xss.type
end
end
+
+ def required_variable_names
+ %w(href type)
+ end
end
end
-
- class ChannelBase
- include Base
- %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
+ class ChannelBase < Base
+ include SetupDefaultDate
- def make_#{element}
- self.class::#{Utils.to_class_name(element)}.new(@maker)
- end
-EOC
+ %w(cloud categories skipDays skipHours).each do |name|
+ def_classed_element(name)
+ end
+
+ %w(generator copyright description title).each do |name|
+ def_classed_element(name, nil, "content")
end
- %w(about title link description language copyright
- managingEditor webMaster rating docs date
- lastBuildDate generator ttl).each do |element|
+ [
+ ["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 ttl).each do |element|
attr_accessor element
add_need_initialize_variable(element)
end
- alias_method(:pubDate, :date)
- alias_method(:pubDate=, :date=)
+ %w(date lastBuildDate).each do |date_element|
+ attr_reader date_element
+ add_need_initialize_variable(date_element)
+ end
- def current_element(rss)
- rss.channel
+ def date=(_date)
+ @date = _parse_date_if_needed(_date)
end
- class SkipDaysBase
- include Base
+ def lastBuildDate=(_date)
+ @lastBuildDate = _parse_date_if_needed(_date)
+ end
- def_array_element("days")
+ def pubDate
+ 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
+ def pubDate=(date)
+ self.date = date
+ end
- class DayBase
- include Base
-
- %w(content).each do |element|
- attr_accessor element
- add_need_initialize_variable(element)
- end
+ def updated
+ date
+ end
- def current_element(rss)
- rss.channel.skipDays.last
- end
+ def updated=(date)
+ self.date = date
+ end
- end
+ alias_method(:rights, :copyright)
+ alias_method(:rights=, :copyright=)
+
+ alias_method(:subtitle, :description)
+ alias_method(:subtitle=, :description=)
+
+ def icon
+ image_favicon.about
end
-
- class SkipHoursBase
- include Base
- def_array_element("hours")
+ def icon=(url)
+ image_favicon.about = 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
+ maker.image.url
+ end
- class HourBase
- include Base
-
+ def logo=(url)
+ maker.image.url = url
+ end
+
+ class SkipDaysBase < Base
+ def_array_element("day")
+
+ class DayBase < Base
%w(content).each do |element|
attr_accessor element
add_need_initialize_variable(element)
end
+ end
+ end
- def current_element(rss)
- rss.channel.skipHours.last
- end
+ class SkipHoursBase < Base
+ def_array_element("hour")
+ class HourBase < Base
+ %w(content).each do |element|
+ attr_accessor element
+ add_need_initialize_variable(element)
+ end
end
end
-
- class CloudBase
- 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
- def_array_element("items")
-
+ 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]
@@ -423,21 +686,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 +702,244 @@ 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(comments id published).each do |element|
attr_accessor element
add_need_initialize_variable(element)
end
- alias_method(:pubDate, :date)
- alias_method(:pubDate=, :date=)
+ %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
+
+ 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
+
+ def permanent_link?
+ isPermaLink
+ end
+
+ def permanent_link=(bool)
+ self.isPermaLink = bool
+ 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
+ include SetupDefaultDate
+
+ %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).each do |element|
attr_accessor element
add_need_initialize_variable(element)
end
+
+ 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
+
+ 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..3559a45ad0 100644
--- a/lib/rss/maker/content.rb
+++ b/lib/rss/maker/content.rb
@@ -1,5 +1,7 @@
+# frozen_string_literal: false
require 'rss/content'
require 'rss/maker/1.0'
+require 'rss/maker/2.0'
module RSS
module Maker
@@ -7,17 +9,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..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,64 +12,41 @@ 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.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 +55,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 +72,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 +87,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..f806cbcaae
--- /dev/null
+++ b/lib/rss/maker/entry.rb
@@ -0,0 +1,164 @@
+# frozen_string_literal: false
+require "rss/maker/atom"
+require "rss/maker/feed"
+
+module RSS
+ module Maker
+ module Atom
+ class Entry < RSSBase
+ def initialize(feed_version="1.0")
+ super
+ @feed_type = "atom"
+ @feed_subtype = "entry"
+ end
+
+ private
+ def make_feed
+ ::RSS::Atom::Entry.new(@version, @encoding, @standalone)
+ end
+
+ def setup_elements(entry)
+ setup_items(entry)
+ end
+
+ class Channel < ChannelBase
+ class SkipDays < SkipDaysBase
+ class Day < DayBase
+ end
+ end
+
+ class SkipHours < SkipHoursBase
+ class Hour < HourBase
+ end
+ end
+
+ class Cloud < CloudBase
+ end
+
+ Categories = Feed::Channel::Categories
+ Links = Feed::Channel::Links
+ Authors = Feed::Channel::Authors
+ Contributors = Feed::Channel::Contributors
+
+ class Generator < GeneratorBase
+ include AtomGenerator
+
+ def self.not_set_name
+ "maker.channel.generator"
+ end
+ end
+
+ Copyright = Feed::Channel::Copyright
+
+ class Description < DescriptionBase
+ end
+
+ Title = Feed::Channel::Title
+ end
+
+ class Image < ImageBase
+ end
+
+ class Items < ItemsBase
+ def to_feed(entry)
+ (normalize.first || Item.new(@maker)).to_feed(entry)
+ end
+
+ class Item < ItemBase
+ def to_feed(entry)
+ set_default_values do
+ setup_values(entry)
+ entry.dc_dates.clear
+ setup_other_elements(entry)
+ unless have_required_values?
+ raise NotSetError.new("maker.item", not_set_required_variables)
+ end
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(id updated)
+ end
+
+ def variables
+ super + ["updated"]
+ end
+
+ def variable_is_set?
+ super or !authors.empty?
+ end
+
+ def not_set_required_variables
+ set_default_values do
+ vars = super
+ if authors.all? {|author| !author.have_required_values?}
+ vars << "author"
+ end
+ vars << "title" unless title {|t| t.have_required_values?}
+ vars
+ end
+ end
+
+ def _set_default_values
+ 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
+ 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[:updated]
+ end
+
+ Guid = Feed::Items::Item::Guid
+ Enclosure = Feed::Items::Item::Enclosure
+ Source = Feed::Items::Item::Source
+ Categories = Feed::Items::Item::Categories
+ Authors = Feed::Items::Item::Authors
+ Contributors = Feed::Items::Item::Contributors
+ Links = Feed::Items::Item::Links
+ Rights = Feed::Items::Item::Rights
+ Description = Feed::Items::Item::Description
+ Title = Feed::Items::Item::Title
+ Content = Feed::Items::Item::Content
+ end
+ end
+
+ class Textinput < TextinputBase
+ end
+ end
+ end
+
+ add_maker("atom:entry", "1.0", Atom::Entry)
+ add_maker("atom1.0:entry", "1.0", Atom::Entry)
+ end
+end
diff --git a/lib/rss/maker/feed.rb b/lib/rss/maker/feed.rb
new file mode 100644
index 0000000000..fdef7ad643
--- /dev/null
+++ b/lib/rss/maker/feed.rb
@@ -0,0 +1,427 @@
+# frozen_string_literal: false
+require "rss/maker/atom"
+
+module RSS
+ module Maker
+ module Atom
+ class Feed < RSSBase
+ def initialize(feed_version="1.0")
+ super
+ @feed_type = "atom"
+ @feed_subtype = "feed"
+ end
+
+ private
+ def make_feed
+ ::RSS::Atom::Feed.new(@version, @encoding, @standalone)
+ end
+
+ def setup_elements(feed)
+ setup_channel(feed)
+ setup_image(feed)
+ setup_items(feed)
+ end
+
+ class Channel < ChannelBase
+ include SetupDefaultLanguage
+
+ 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,
+ }
+ self.id ||= about
+ super(&block)
+ ensure
+ self.id = keep[:id]
+ 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)
+ entry.dc_dates.clear
+ 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,
+ }
+ self.id ||= link
+ super(&block)
+ ensure
+ self.id = keep[:id]
+ end
+
+ class Guid < GuidBase
+ def to_feed(feed, current)
+ end
+ end
+
+ class Enclosure < EnclosureBase
+ def to_feed(feed, current)
+ end
+ end
+
+ class Source < SourceBase
+ def to_feed(feed, current)
+ source = current.class::Source.new
+ setup_values(source)
+ current.source = source
+ set_parent(source, current)
+ setup_other_elements(feed, source)
+ current.source = nil if source.to_s == "<source/>"
+ end
+
+ private
+ def required_variable_names
+ []
+ end
+
+ def variables
+ super + ["updated"]
+ end
+
+ AtomPersons.def_atom_persons(self, "author",
+ "maker.item.source.author")
+ AtomPersons.def_atom_persons(self, "contributor",
+ "maker.item.source.contributor")
+
+ class Categories < CategoriesBase
+ class Category < CategoryBase
+ include AtomCategory
+
+ def self.not_set_name
+ "maker.item.source.category"
+ end
+ end
+ end
+
+ class Generator < GeneratorBase
+ include AtomGenerator
+
+ def self.not_set_name
+ "maker.item.source.generator"
+ end
+ end
+
+ class Icon < IconBase
+ def to_feed(feed, current)
+ icon = current.class::Icon.new
+ class << icon
+ alias_method(:url=, :content=)
+ end
+ set = setup_values(icon)
+ class << icon
+ remove_method(:url=)
+ end
+ if set
+ current.icon = icon
+ set_parent(icon, current)
+ setup_other_elements(feed, icon)
+ elsif variable_is_set?
+ raise NotSetError.new("maker.item.source.icon",
+ not_set_required_variables)
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(url)
+ end
+ end
+
+ class Links < LinksBase
+ class Link < LinkBase
+ include AtomLink
+
+ def self.not_set_name
+ "maker.item.source.link"
+ end
+ end
+ end
+
+ class Logo < LogoBase
+ include AtomLogo
+
+ def self.not_set_name
+ "maker.item.source.logo"
+ end
+ end
+
+ maker_name_base = "maker.item.source."
+ maker_name = "#{maker_name_base}rights"
+ AtomTextConstruct.def_atom_text_construct(self, "rights",
+ maker_name)
+ maker_name = "#{maker_name_base}subtitle"
+ AtomTextConstruct.def_atom_text_construct(self, "subtitle",
+ maker_name)
+ maker_name = "#{maker_name_base}title"
+ AtomTextConstruct.def_atom_text_construct(self, "title",
+ maker_name)
+ end
+
+ class Categories < CategoriesBase
+ class Category < CategoryBase
+ include AtomCategory
+
+ def self.not_set_name
+ "maker.item.category"
+ end
+ end
+ end
+
+ AtomPersons.def_atom_persons(self, "author", "maker.item.author")
+ AtomPersons.def_atom_persons(self, "contributor",
+ "maker.item.contributor")
+
+ class Links < LinksBase
+ class Link < LinkBase
+ include AtomLink
+
+ def self.not_set_name
+ "maker.item.link"
+ end
+ end
+ end
+
+ AtomTextConstruct.def_atom_text_construct(self, "rights",
+ "maker.item.rights")
+ AtomTextConstruct.def_atom_text_construct(self, "summary",
+ "maker.item.description",
+ "Description")
+ AtomTextConstruct.def_atom_text_construct(self, "title",
+ "maker.item.title")
+
+ class Content < ContentBase
+ def to_feed(feed, current)
+ content = current.class::Content.new
+ if setup_values(content)
+ content.src = nil if content.src and content.content
+ current.content = content
+ set_parent(content, current)
+ setup_other_elements(feed, content)
+ elsif variable_is_set?
+ raise NotSetError.new("maker.item.content",
+ not_set_required_variables)
+ end
+ end
+
+ alias_method(:xml, :xml_content)
+
+ private
+ def required_variable_names
+ if out_of_line?
+ %w(type)
+ elsif xml_type?
+ %w(xml_content)
+ else
+ %w(content)
+ end
+ end
+
+ def variables
+ if out_of_line?
+ super
+ elsif xml_type?
+ super + %w(xml)
+ else
+ super
+ end
+ end
+
+ def xml_type?
+ _type = type
+ return false if _type.nil?
+ _type == "xhtml" or
+ /(?:\+xml|\/xml)$/i =~ _type or
+ %w(text/xml-external-parsed-entity
+ application/xml-external-parsed-entity
+ application/xml-dtd).include?(_type.downcase)
+ end
+ end
+ end
+ end
+
+ class Textinput < TextinputBase
+ end
+ end
+ end
+
+ add_maker("atom", "1.0", Atom::Feed)
+ add_maker("atom:feed", "1.0", Atom::Feed)
+ add_maker("atom1.0", "1.0", Atom::Feed)
+ add_maker("atom1.0:feed", "1.0", Atom::Feed)
+ end
+end
diff --git a/lib/rss/maker/image.rb b/lib/rss/maker/image.rb
index ed51c8ecba..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'
@@ -9,24 +10,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 +37,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 +54,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,67 +77,36 @@ 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
class ChannelBase; include Maker::ImageFaviconModel; end
-
+
class ItemsBase
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
- 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
+ makers.each do |maker|
+ maker.module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ class Channel
+ ImageFaviconModel.install_image_favicon(self)
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..d964a4d942
--- /dev/null
+++ b/lib/rss/maker/itunes.rb
@@ -0,0 +1,243 @@
+# frozen_string_literal: false
+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 :explicit_clean_other
+ def_explicit_clean_other_accessor(klass, full_name)
+ when :csv
+ def_csv_accessor(klass, full_name)
+ when :element, :attribute
+ recommended_attribute_name, = *args
+ klass_name = "ITunes#{Utils.to_class_name(name)}"
+ klass.def_classed_element(full_name, klass_name,
+ recommended_attribute_name)
+ when :elements
+ plural_name, recommended_attribute_name = args
+ plural_name ||= "#{name}s"
+ full_plural_name = "#{RSS::ITUNES_PREFIX}_#{plural_name}"
+ klass_name = "ITunes#{Utils.to_class_name(name)}"
+ plural_klass_name = "ITunes#{Utils.to_class_name(plural_name)}"
+ def_elements_class_accessor(klass, name, full_name, full_plural_name,
+ klass_name, plural_klass_name,
+ recommended_attribute_name)
+ end
+ end
+
+ def def_yes_other_accessor(klass, full_name)
+ klass.def_other_element(full_name)
+ klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ def #{full_name}?
+ Utils::YesOther.parse(@#{full_name})
+ end
+ EOC
+ end
+
+ def def_explicit_clean_other_accessor(klass, full_name)
+ klass.def_other_element(full_name)
+ klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ def #{full_name}?
+ Utils::ExplicitCleanOther.parse(#{full_name})
+ end
+ EOC
+ end
+
+ def def_csv_accessor(klass, full_name)
+ klass.def_csv_element(full_name)
+ end
+
+ def def_elements_class_accessor(klass, name, full_name, full_plural_name,
+ klass_name, plural_klass_name,
+ recommended_attribute_name=nil)
+ if recommended_attribute_name
+ klass.def_classed_elements(full_name, recommended_attribute_name,
+ plural_klass_name, full_plural_name)
+ else
+ klass.def_classed_element(full_plural_name, plural_klass_name)
+ end
+ klass.module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ def new_#{full_name}(text=nil)
+ #{full_name} = @#{full_plural_name}.new_#{name}
+ #{full_name}.text = text
+ if block_given?
+ yield #{full_name}
+ else
+ #{full_name}
+ end
+ end
+ EOC
+ end
+ end
+
+ module ITunesChannelModel
+ extend ITunesBaseModel
+
+ class << self
+ def append_features(klass)
+ super
+
+ ::RSS::ITunesChannelModel::ELEMENT_INFOS.each do |name, type, *args|
+ def_class_accessor(klass, name, type, *args)
+ end
+ end
+ end
+
+ class ITunesCategoriesBase < Base
+ def_array_element("category", "itunes_categories",
+ "ITunesCategory")
+ class ITunesCategoryBase < Base
+ attr_accessor :text
+ add_need_initialize_variable("text")
+ def_array_element("category", "itunes_categories",
+ "ITunesCategory")
+
+ def have_required_values?
+ text
+ end
+
+ alias_method :to_feed_for_categories, :to_feed
+ def to_feed(feed, current)
+ if text and current.respond_to?(:itunes_category)
+ new_item = current.class::ITunesCategory.new(text)
+ to_feed_for_categories(feed, new_item)
+ current.itunes_categories << new_item
+ end
+ end
+ end
+ end
+
+ class ITunesImageBase < Base
+ add_need_initialize_variable("href")
+ attr_accessor("href")
+
+ def to_feed(feed, current)
+ if @href and current.respond_to?(:itunes_image)
+ current.itunes_image ||= current.class::ITunesImage.new
+ current.itunes_image.href = @href
+ end
+ end
+ end
+
+ class ITunesOwnerBase < Base
+ %w(itunes_name itunes_email).each do |name|
+ add_need_initialize_variable(name)
+ attr_accessor(name)
+ end
+
+ def to_feed(feed, current)
+ if current.respond_to?(:itunes_owner=)
+ _not_set_required_variables = not_set_required_variables
+ if (required_variable_names - _not_set_required_variables).empty?
+ return
+ end
+
+ unless have_required_values?
+ raise NotSetError.new("maker.channel.itunes_owner",
+ _not_set_required_variables)
+ end
+ current.itunes_owner ||= current.class::ITunesOwner.new
+ current.itunes_owner.itunes_name = @itunes_name
+ current.itunes_owner.itunes_email = @itunes_email
+ end
+ end
+
+ private
+ def required_variable_names
+ %w(itunes_name itunes_email)
+ end
+ end
+ end
+
+ module ITunesItemModel
+ extend ITunesBaseModel
+
+ class << self
+ def append_features(klass)
+ super
+
+ ::RSS::ITunesItemModel::ELEMENT_INFOS.each do |name, type, *args|
+ def_class_accessor(klass, name, type, *args)
+ end
+ end
+ end
+
+ class ITunesDurationBase < Base
+ attr_reader :content
+ add_need_initialize_variable("content")
+
+ %w(hour minute second).each do |name|
+ attr_reader(name)
+ add_need_initialize_variable(name, 0)
+ end
+
+ def content=(content)
+ if content.nil?
+ @hour, @minute, @second, @content = nil
+ else
+ @hour, @minute, @second =
+ ::RSS::ITunesItemModel::ITunesDuration.parse(content)
+ @content = content
+ end
+ end
+
+ def hour=(hour)
+ @hour = Integer(hour)
+ update_content
+ end
+
+ def minute=(minute)
+ @minute = Integer(minute)
+ update_content
+ end
+
+ def second=(second)
+ @second = Integer(second)
+ update_content
+ end
+
+ def to_feed(feed, current)
+ if @content and current.respond_to?(:itunes_duration=)
+ current.itunes_duration ||= current.class::ITunesDuration.new
+ current.itunes_duration.content = @content
+ end
+ end
+
+ private
+ def update_content
+ components = [@hour, @minute, @second]
+ @content =
+ ::RSS::ITunesItemModel::ITunesDuration.construct(*components)
+ end
+ end
+ end
+
+ class ChannelBase
+ include Maker::ITunesChannelModel
+ class ITunesCategories < ITunesCategoriesBase
+ class ITunesCategory < ITunesCategoryBase
+ ITunesCategory = self
+ end
+ end
+
+ class ITunesImage < ITunesImageBase; end
+ class ITunesOwner < ITunesOwnerBase; end
+ end
+
+ class ItemsBase
+ class ItemBase
+ include Maker::ITunesItemModel
+ class ITunesDuration < ITunesDurationBase; end
+ end
+ end
+ end
+end
diff --git a/lib/rss/maker/slash.rb b/lib/rss/maker/slash.rb
new file mode 100644
index 0000000000..3bd82d3057
--- /dev/null
+++ b/lib/rss/maker/slash.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: false
+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..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'
@@ -7,17 +8,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..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'
@@ -8,24 +9,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 +30,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,85 +41,44 @@ 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("topic", nil, "TaxonomyTopic")
+ alias_method(:new_taxo_topic, :new_topic) # For backward compatibility
- 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
-
+
attr_accessor :value
add_need_initialize_variable("value")
alias_method(:taxo_link, :value)
alias_method(:taxo_link=, :value=)
-
+
def have_required_values?
@value
end
@@ -140,43 +89,31 @@ EOC
class RSSBase
include TaxonomyTopicModel
end
-
+
class ChannelBase
include TaxonomyTopicsModel
end
-
+
class ItemsBase
class ItemBase
include TaxonomyTopicsModel
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..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'
@@ -8,123 +9,54 @@ 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")
-
+
alias_method(:resource, :value)
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..a9842e6d40 100644
--- a/lib/rss/parser.rb
+++ b/lib/rss/parser.rb
@@ -1,7 +1,9 @@
+# frozen_string_literal: false
require "forwardable"
require "open-uri"
require "rss/rss"
+require "rss/xml"
module RSS
@@ -33,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
@@ -97,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)
@@ -112,13 +114,13 @@ 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)
begin
- URI(rss)
+ ::URI.parse(rss)
rescue ::URI::Error
rss
end
@@ -132,7 +134,7 @@ module RSS
listener.raise_for_undefined_entity?
end
end
-
+
def initialize(rss)
@listener = self.class.listener.new
@rss = rss
@@ -173,35 +175,35 @@ 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
- rescue NameError
- []
- end
+ (@@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)
@@ -216,52 +218,48 @@ module RSS
# retrieve class_name for the supplied uri and tag_name
# If it doesn't exist, capitalize the tag_name
def class_name(uri, tag_name)
- begin
- @@class_names[uri][tag_name]
- rescue NameError
- tag_name[0,1].upcase + tag_name[1..-1]
- end
+ name = (@@class_names[uri] || {})[tag_name]
+ return name if name
+
+ tag_name = tag_name.gsub(/[_\-]([a-z]?)/) {$1.upcase}
+ tag_name[0, 1].upcase + tag_name[1..-1]
end
- def install_get_text_element(uri, name, 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
-
+
def raise_for_undefined_entity?
true
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,15 +269,18 @@ 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
def xmldecl(version, encoding, standalone)
@version, @encoding, @standalone = version, encoding, standalone
@@ -289,7 +290,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 +312,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 +361,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,12 +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 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
@@ -370,26 +404,56 @@ 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)
- if @do_validate
- if _ns(ns, prefix) == require_uri
- #ns.delete(prefix)
- else
+ 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
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 setter and @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,16 +461,22 @@ 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)
+ 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
- check_ns(tag_name, prefix, ns, klass.required_uri)
-
+ def collect_attributes(tag_name, prefix, attrs, ns, klass)
attributes = {}
klass.get_attributes.each do |a_name, a_uri, required, element_name|
-
if a_uri.is_a?(String) or !a_uri.respond_to?(:include?)
a_uri = [a_uri]
end
@@ -435,24 +505,48 @@ module RSS
attributes[a_name] = val
end
+ attributes
+ end
+ def setup_next_element(tag_name, klass, attributes)
previous = @last_element
next_element = klass.new(@do_validate, attributes)
- previous.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?
+
+ 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 = text.lstrip.unpack("m").first
+ end
+ @last_element.content = text
+ end
+ end
if @do_validate
@last_element.validate_for_stream(tags, @ignore_unknown_element)
end
@last_element = previous
- }
+ end
end
+ 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],
@@ -460,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 a06985af94..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)
@@ -11,11 +20,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)
+ force_zone!(time, zone, off)
+ time
else
+ datetime << usec
Time.local(*datetime)
end
else
@@ -25,11 +42,26 @@ class Time
end
end
- unless instance_methods.include?("w3cdtf")
- alias w3cdtf iso8601
+ 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 = strftime('%6N').index(/0*\z/)
+ end
+ xmlschema(fraction_digits)
+ end
end
end
+
require "English"
require "rss/utils"
require "rss/converter"
@@ -37,14 +69,20 @@ require "rss/xml-stylesheet"
module RSS
- VERSION = "0.1.6"
+ # 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)
@@ -52,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)
@@ -62,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)
@@ -70,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)
@@ -78,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)
@@ -86,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)
@@ -94,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)
@@ -107,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)
@@ -116,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)
@@ -128,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)
@@ -136,17 +192,27 @@ module RSS
super("required variables of #{@name} are not set: #{@variables.join(', ')}")
end
end
-
- module BaseModel
+ # Raised when a RSS::Maker attempts to use an unknown maker.
+ 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 +230,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,26 +245,32 @@ 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
+ self::ELEMENTS << name unless self::ELEMENTS.include?(name)
add_need_initialize_variable(name)
install_model(tag_name, uri, occurs, name)
- def_corresponded_attr_writer name, type, disp_name
- 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
rv << value
end
- rv << "</#{elem_name}>"
+ rv << "</#{elem_name}>"
rv
else
''
@@ -218,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}
@@ -229,7 +301,7 @@ EOC
else
rv << value
end
- rv << "</#{elem_name}>"
+ rv << "</#{elem_name}>"
rv
else
''
@@ -252,26 +324,106 @@ EOC
EOC
end
- def convert_attr_reader(*attrs)
+ def inherit_convert_attr_reader(*attrs)
+ attrs.each do |attr|
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
+ def #{attr}_without_inherit
+ convert(@#{attr})
+ end
+
+ def #{attr}
+ if @#{attr}
+ #{attr}_without_inherit
+ elsif @parent
+ @parent.#{attr}
+ else
+ nil
+ end
+ end
+EOC
+ end
+ end
+
+ def uri_convert_attr_reader(*attrs)
attrs.each do |attr|
- attr = attr.id2name if attr.kind_of?(Integer)
module_eval(<<-EOC, *get_file_and_line_from_caller(2))
+ def #{attr}_without_base
+ convert(@#{attr})
+ end
+
def #{attr}
- if @converter
- @converter.convert(@#{attr})
+ value = #{attr}_without_base
+ return nil if value.nil?
+ if /\\A[a-z][a-z0-9+.\\-]*:/i =~ value
+ value
else
- @#{attr}
+ "\#{base}\#{value}"
end
end
EOC
end
end
+ def convert_attr_reader(*attrs)
+ attrs.each do |attr|
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
+ def #{attr}
+ convert(@#{attr})
+ end
+EOC
+ end
+ end
+
+ def explicit_clean_other_attr_reader(*attrs)
+ attrs.each do |attr|
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
+ attr_reader(:#{attr})
+ def #{attr}?
+ ExplicitCleanOther.parse(@#{attr})
+ end
+ EOC
+ end
+ end
+
+ def yes_other_attr_reader(*attrs)
+ attrs.each do |attr|
+ 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|
+ 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 +435,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 +518,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 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)
+ @#{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 +595,11 @@ EOC
end
def #{accessor_name}=(*args)
- warn("Warning:\#{caller.first.sub(/:in `.*'\z/, '')}: " \
- "Don't use `#{accessor_name} = XXX'/`set_#{accessor_name}(XXX)'. " \
+ receiver = self.class.name
+ warn("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.", uplevel: 1)
if args.size == 1
@#{accessor_name}.push(args[0])
else
@@ -394,13 +611,64 @@ 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 = " "
-
+
MUST_CALL_VALIDATORS = {}
MODELS = []
GET_ATTRIBUTES = []
@@ -408,140 +676,149 @@ 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", [])
- 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)
+ end
- klass.module_eval(<<-EOC)
- public
-
- @tag_name = name.split(/::/).last
- @tag_name[0,1] = @tag_name[0,1].downcase
- @have_content = false
+ def install_must_call_validator(prefix, uri)
+ self::MUST_CALL_VALIDATORS[uri] = prefix
+ 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
- end
- def self.plural_forms
- super.merge(PLURAL_FORMS)
+ def install_model(tag, uri, occurs=nil, getter=nil, plural=false)
+ getter ||= tag
+ if m = self::MODELS.find {|t, u, o, g, p| t == tag and u == uri}
+ m[2] = occurs
+ else
+ self::MODELS << [tag, uri, occurs, getter, plural]
end
+ end
-
- def self.install_must_call_validator(prefix, uri)
- MUST_CALL_VALIDATORS[uri] = prefix
+ 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
-
- 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
+ self::GET_ATTRIBUTES << [name, uri, required, element_name]
+ add_need_initialize_variable(disp_name)
+ end
+
+ def def_corresponded_attr_writer(name, type=nil, disp_name=nil)
+ disp_name ||= name
+ case type
+ when :integer
+ integer_writer name, disp_name
+ when :positive_integer
+ positive_integer_writer name, disp_name
+ when :boolean
+ boolean_writer name, disp_name
+ when :w3cdtf, :rfc822, :rfc2822
+ date_writer name, type, disp_name
+ when :text_type
+ text_type_writer name, disp_name
+ when :content
+ content_writer name, disp_name
+ when :explicit_clean_other
+ explicit_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 :explicit_clean_other
+ explicit_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 +828,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 +845,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
@@ -579,7 +864,7 @@ EOC
def full_name
tag_name
end
-
+
def converter=(converter)
@converter = converter
targets = children.dup
@@ -598,31 +883,37 @@ 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)
validate_attribute
__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 +926,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
+ not child.nil?
+ end
+ else
+ true
+ end
+ end
+ end
+
private
def initialize_variables(attrs)
normalized_attrs = {}
@@ -646,16 +975,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 +994,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,13 +1012,14 @@ EOC
else
content = content.reject{|x| x.empty?}
if content.empty?
+ return "" if attrs.empty?
end_tag = "/>"
else
start_tag << ">\n"
end_tag = "\n#{indent}</#{full_name}>"
end
end
-
+
start_tag + content.join("\n") + end_tag
end
@@ -712,7 +1044,7 @@ EOC
end
attrs
end
-
+
def tag_name_with_prefix(prefix)
"#{prefix}:#{tag_name}"
end
@@ -722,56 +1054,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 +1069,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 +1099,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 +1107,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
@@ -844,10 +1128,10 @@ EOC
end
models.each_with_index do |model, i|
- name, model_uri, occurs, getter = model
+ name, _, occurs, = model
if DEBUG
- p "before"
+ p "before"
p tags
p model
end
@@ -933,23 +1217,51 @@ 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 = [_content].pack("m0") if need_base64_encode?
+ h(_content)
+ end
+ end
end
module RootElementMixin
include XMLStyleSheetMixin
-
- attr_reader :output_encoding
- def initialize(rss_version, version=nil, encoding=nil, standalone=nil)
+ attr_reader :output_encoding
+ attr_reader :feed_type, :feed_subtype, :feed_version
+ attr_accessor :version, :encoding, :standalone
+ def initialize(feed_version, version=nil, encoding=nil, standalone=nil)
super()
- @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 +1276,48 @@ EOC
xss.setup_maker(maker)
end
- setup_maker_elements(maker)
+ super
+ end
+
+ def to_feed(type, &block)
+ Maker.make(type) do |maker|
+ setup_maker(maker)
+ block.call(maker) if block
+ end
+ end
+
+ def to_rss(type, &block)
+ to_feed("rss#{type}", &block)
+ end
+
+ def to_atom(type, &block)
+ to_feed("atom:#{type}", &block)
end
- def to_xml(version=nil, &block)
- if version.nil? or version == @rss_version
+ 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
@@ -994,7 +1329,7 @@ EOC
rv << "?>\n"
rv
end
-
+
def ns_declarations
decls = {}
self.class::NSPOOL.collect do |prefix, uri|
@@ -1003,13 +1338,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..0055fc9f88
--- /dev/null
+++ b/lib/rss/slash.rb
@@ -0,0 +1,52 @@
+# 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)
+
+ 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..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
@@ -29,21 +31,24 @@ 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
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
@@ -58,7 +63,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..b7ea219e8c 100644
--- a/lib/rss/taxonomy.rb
+++ b/lib/rss/taxonomy.rb
@@ -1,18 +1,21 @@
+# 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|
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
@@ -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 b242a72292..9203df9a9b 100644
--- a/lib/rss/utils.rb
+++ b/lib/rss/utils.rb
@@ -1,25 +1,110 @@
+# 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|
+ 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(':')
- [file, line.to_i]
+ line = line.to_i
+ line += 1 if i.zero?
+ [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 &amp; Dragons"
+ # RSS::Utils.h(">_>")
+ # # => "&gt;_&gt;"
def html_escape(s)
s.to_s.gsub(/&/, "&amp;").gsub(/\"/, "&quot;").gsub(/>/, "&gt;").gsub(/</, "&lt;")
end
alias h html_escape
-
+
# If +value+ is an instance of class +klass+, return it, else
# create a new instance of +klass+ with value +value+.
def new_with_value_if_need(klass, value)
@@ -30,8 +115,86 @@ 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 ExplicitCleanOther
+ module_function
+ def parse(value)
+ if [true, false, nil].include?(value)
+ value
+ else
+ case value.to_s
+ when /\Aexplicit|yes|true\z/i
+ true
+ when /\Aclean|no|false\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..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|
@@ -35,6 +36,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)
@@ -90,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
new file mode 100644
index 0000000000..cda8668044
--- /dev/null
+++ b/lib/rss/xml.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: false
+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/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