# frozen_string_literal: false
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
(\d\d):(\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]
usec = $7.to_f * 1000000 if $7
off = zone_offset(zone, datetime[0])
datetime = apply_offset(*(datetime + [off]))
time = Time.utc(*datetime)
force_zone!(time, zone, off)
raise ArgumentError.new("invalid date: #{date.inspect}")
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
# # => "2013-08-26T14:12:10.817124-07:00"
fraction_digits = strftime('%6N').index(/0*\z/)
xmlschema(fraction_digits)
require_relative "converter"
require_relative "xml-stylesheet"
# The URI of the RSS 1.0 specification
URI = "http://purl.org/rss/1.0/"
# 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
# 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)
@tag, @parent = tag, parent
super("tag <#{tag}> is missing in tag <#{parent}>")
# 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
class TooMuchTagError < InvalidRSSError
attr_reader :tag, :parent
def initialize(tag, parent)
@tag, @parent = tag, parent
super("tag <#{tag}> is too much in tag <#{parent}>")
# 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)
@tag, @attribute = tag, attribute
super("attribute <#{attribute}> is missing in tag <#{tag}>")
# 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
super("tag <#{tag}> is unknown in namespace specified by uri <#{uri}>")
# Raised when an unexpected tag is encountered.
class NotExpectedTagError < InvalidRSSError
attr_reader :tag, :uri, :parent
def initialize(tag, uri, parent)
@tag, @uri, @parent = tag, uri, parent
super("tag <{#{uri}}#{tag}> is not expected in tag <#{parent}>")
# For backward compatibility :X
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)
@tag, @value, @attribute = tag, value, attribute
message = "value <#{value}> of "
message << "attribute <#{attribute}> of " if attribute
message << "tag <#{tag}> is not available."
# Raised when an unknown conversion error occurs.
class UnknownConversionMethodError < Error
super("can't convert to #{to} from #{from}.")
# for backward compatibility
UnknownConvertMethod = UnknownConversionMethodError # :nodoc:
# Raised when a conversion failure occurs.
class ConversionError < Error
attr_reader :string, :to, :from
def initialize(string, to, from)
super("can't convert #{@string} to #{to} from #{from}.")
# Raised when a required variable is not set.
class NotSetError < Error
attr_reader :name, :variables
def initialize(name, variables)
super("required variables of #{@name} are not set: #{@variables.join(', ')}")
# Raised when a RSS::Maker attempts to use an unknown maker.
class UnsupportedMakerVersionError < Error
super("Maker doesn't support version: #{@version}")
def install_have_child_element(tag_name, uri, occurs, name=nil, type=nil)
add_need_initialize_variable(name)
install_model(tag_name, uri, occurs, 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|
"\#{@#{n}.to_s(need_convert, indent)}"
alias_method(:install_have_attribute_element, :install_have_child_element)
def install_have_children_element(tag_name, uri, occurs, name=nil, plural_name=nil)
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, true)
def_children_accessor(name, plural_name)
install_element(name, "s") do |n, elem_name|
value = "\#{x.to_s(need_convert, indent)}"
rv << value if /\\A\\s*\\z/ !~ value
def install_text_element(tag_name, uri, occurs, name=nil, type=nil,
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)
def_corresponded_attr_reader(name, type || :convert)
install_element(name) do |n, elem_name|
if respond_to?(:#{n}_content)
rv = "\#{indent}<#{elem_name}>"
value = html_escape(content)
def install_date_element(tag_name, uri, occurs, name=nil, type=nil, disp_name=nil)
add_need_initialize_variable(name)
install_model(tag_name, uri, occurs, name)
date_writer(name, type, disp_name)
install_element(name) do |n, elem_name|
rv = "\#{indent}<#{elem_name}>"
value = html_escape(@#{n}.#{type})
def install_element(name, postfix="")
elem_name = name.sub('_', ':')
method_name = "#{name}_element#{postfix}"
add_to_element_method(method_name)
module_eval(<<-EOC, *get_file_and_line_from_caller(2))
def #{method_name}(need_convert=true, indent='')
#{yield(name, elem_name)}
def inherit_convert_attr_reader(*attrs)
module_eval(<<-EOC, *get_file_and_line_from_caller(2))
def #{attr}_without_inherit
def uri_convert_attr_reader(*attrs)
module_eval(<<-EOC, *get_file_and_line_from_caller(2))
value = #{attr}_without_base
if /\\A[a-z][a-z0-9+.\\-]*:/i =~ value
def convert_attr_reader(*attrs)
module_eval(<<-EOC, *get_file_and_line_from_caller(2))
def explicit_clean_other_attr_reader(*attrs)
module_eval(<<-EOC, __FILE__, __LINE__ + 1)
ExplicitCleanOther.parse(@#{attr})
def yes_other_attr_reader(*attrs)
module_eval(<<-EOC, __FILE__, __LINE__ + 1)
Utils::YesOther.parse(@#{attr})
def csv_attr_reader(*attrs)
if attrs.last.is_a?(Hash)
separator = options[:separator]
module_eval(<<-EOC, __FILE__, __LINE__ + 1)
@#{attr}.join(#{separator.dump})
def date_writer(name, type, disp_name=name)
module_eval(<<-EOC, *get_file_and_line_from_caller(2))
elsif new_value.kind_of?(Time)
@#{name} = Time.__send__('#{type}', new_value)
raise NotAvailableValueError.new('#{disp_name}', new_value)
if /\\A\\s*\\z/ !~ new_value.to_s
unless Date._parse(new_value, false).empty?
@#{name} = Time.parse(new_value)
alias_method(:to_s, :#{type})
def integer_writer(name, disp_name=name)
module_eval(<<-EOC, *get_file_and_line_from_caller(2))
@#{name} = Integer(new_value)
raise NotAvailableValueError.new('#{disp_name}', new_value)
@#{name} = new_value.to_i
def positive_integer_writer(name, disp_name=name)
module_eval(<<-EOC, *get_file_and_line_from_caller(2))
raise ArgumentError if tmp <= 0
raise NotAvailableValueError.new('#{disp_name}', new_value)
@#{name} = new_value.to_i
def boolean_writer(name, disp_name=name)
module_eval(<<-EOC, *get_file_and_line_from_caller(2))