1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/lib/rss/rss.rb
kou 5e64882123 rss itunes: fix a bug that <itunes:explicit> value isn't fully supported
Fix GH-1725

<itunes:explicit> accepts "explicit", "yes", "true", "clean", "no" and
"false" as valid values.

Here is the <itunes:explicit>'s description in
https://help.apple.com/itc/podcasts_connect/#/itcb54353390:

> The <itunes:explicit> tag indicates whether your podcast contains
> explicit material. You can specify the following values:
>
>   * Yes | Explicit | True. If you specify yes, explicit, or true,
>     indicating the presence of explicit content, the iTunes Store
>     displays an Explicit parental advisory graphic for your podcast.
>
>   * Clean | No | False. If you specify clean, no, or false, indicating
>     that none of your podcast episodes contain explicit language or
>     adult content, the iTunes Store displays a Clean parental
>     advisory graphic for your podcast.

I don't know whether <itunes:explicit> value is case sensitive or
insensitive. But the current implementation is case insensitive.

Reported by Valerie Woolard Srinivasan. Thanks!!!


git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@60367 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2017-10-22 16:03:57 +00:00

1347 lines
35 KiB
Ruby

# 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)
(?:T
(\d\d):(\d\d)(?::(\d\d))?
(\.\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]
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
raise ArgumentError.new("invalid date: #{date.inspect}")
end
end
end
end
unless method_defined?(:w3cdtf)
# This method converts a Time object to a String. The String contains the
# time in W3CDTF date/time format.
#
# The W3CDTF format is defined here: http://www.w3.org/TR/NOTE-datetime
#
# Time.now.w3cdtf
# # => "2013-08-26T14:12:10.817124-07:00"
def w3cdtf
if usec.zero?
fraction_digits = 0
else
fraction_digits = strftime('%6N').index(/0*\z/)
end
xmlschema(fraction_digits)
end
end
end
require "English"
require "rss/utils"
require "rss/converter"
require "rss/xml-stylesheet"
module RSS
# 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 # :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)
@prefix = prefix
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)
@tag, @parent = tag, parent
super("tag <#{tag}> is missing in tag <#{parent}>")
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)
@tag, @parent = tag, parent
super("tag <#{tag}> is too much in tag <#{parent}>")
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)
@tag, @attribute = tag, attribute
super("attribute <#{attribute}> is missing in tag <#{tag}>")
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)
@tag, @uri = tag, uri
super("tag <#{tag}> is unknown in namespace specified by uri <#{uri}>")
end
end
# 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}>")
end
end
# 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."
super(message)
end
end
# Raised when an unknown conversion error occurs.
class UnknownConversionMethodError < Error
attr_reader :to, :from
def initialize(to, from)
@to = to
@from = from
super("can't convert to #{to} from #{from}.")
end
end
# 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)
@string = string
@to = to
@from = from
super("can't convert #{@string} to #{to} from #{from}.")
end
end
# Raised when a required variable is not set.
class NotSetError < Error
attr_reader :name, :variables
def initialize(name, variables)
@name = name
@variables = variables
super("required variables of #{@name} are not set: #{@variables.join(', ')}")
end
end
# 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, type=nil)
name ||= tag_name
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|
<<-EOC
if @#{n}
"\#{@#{n}.to_s(need_convert, indent)}"
else
''
end
EOC
end
end
alias_method(:install_have_attribute_element, :install_have_child_element)
def install_have_children_element(tag_name, uri, occurs, name=nil, plural_name=nil)
name ||= tag_name
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|
<<-EOC
rv = []
@#{n}.each do |x|
value = "\#{x.to_s(need_convert, indent)}"
rv << value if /\\A\\s*\\z/ !~ value
end
rv.join("\n")
EOC
end
end
def install_text_element(tag_name, uri, occurs, name=nil, type=nil,
disp_name=nil)
name ||= tag_name
disp_name ||= 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)
def_corresponded_attr_reader(name, type || :convert)
install_element(name) do |n, elem_name|
<<-EOC
if respond_to?(:#{n}_content)
content = #{n}_content
else
content = @#{n}
end
if content
rv = "\#{indent}<#{elem_name}>"
value = html_escape(content)
if need_convert
rv << convert(value)
else
rv << value
end
rv << "</#{elem_name}>"
rv
else
''
end
EOC
end
end
def install_date_element(tag_name, uri, occurs, name=nil, type=nil, disp_name=nil)
name ||= tag_name
type ||= :w3cdtf
disp_name ||= name
self::ELEMENTS << name
add_need_initialize_variable(name)
install_model(tag_name, uri, occurs, name)
# accessor
convert_attr_reader name
date_writer(name, type, disp_name)
install_element(name) do |n, elem_name|
<<-EOC
if @#{n}
rv = "\#{indent}<#{elem_name}>"
value = html_escape(@#{n}.#{type})
if need_convert
rv << convert(value)
else
rv << value
end
rv << "</#{elem_name}>"
rv
else
''
end
EOC
end
end
private
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)}
end
private :#{method_name}
EOC
end
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|
module_eval(<<-EOC, *get_file_and_line_from_caller(2))
def #{attr}_without_base
convert(@#{attr})
end
def #{attr}
value = #{attr}_without_base
return nil if value.nil?
if /\\A[a-z][a-z0-9+.\\-]*:/i =~ value
value
else
"\#{base}\#{value}"
end
end
EOC
end
end
def convert_attr_reader(*attrs)
attrs.each do |attr|
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?
@#{name} = new_value
elsif new_value.kind_of?(Time)
@#{name} = new_value.dup
else
if @do_validate
begin
@#{name} = Time.__send__('#{type}', new_value)
rescue ArgumentError
raise NotAvailableValueError.new('#{disp_name}', new_value)
end
else
@#{name} = nil
if /\\A\\s*\\z/ !~ new_value.to_s
begin
unless Date._parse(new_value, false).empty?
@#{name} = Time.parse(new_value)
end
rescue ArgumentError
end
end
end
end
# Is it need?
if @#{name}
class << @#{name}
undef_method(:to_s)
alias_method(:to_s, :#{type})
end
end
end
EOC
end
def integer_writer(name, disp_name=name)
module_eval(<<-EOC, *get_file_and_line_from_caller(2))
def #{name}=(new_value)
if new_value.nil?
@#{name} = new_value
else
if @do_validate
begin
@#{name} = Integer(new_value)
rescue ArgumentError
raise NotAvailableValueError.new('#{disp_name}', new_value)
end
else
@#{name} = new_value.to_i
end
end
end
EOC
end
def positive_integer_writer(name, disp_name=name)
module_eval(<<-EOC, *get_file_and_line_from_caller(2))
def #{name}=(new_value)
if new_value.nil?
@#{name} = new_value
else
if @do_validate
begin
tmp = Integer(new_value)
raise ArgumentError if tmp <= 0
@#{name} = tmp
rescue ArgumentError
raise NotAvailableValueError.new('#{disp_name}', new_value)
end
else
@#{name} = new_value.to_i
end
end
end
EOC
end
def boolean_writer(name, disp_name=name)
module_eval(<<-EOC, *get_file_and_line_from_caller(2))
def #{name}=(new_value)
if new_value.nil?
@#{name} = new_value
else
if @do_validate and
![true, false, "true", "false"].include?(new_value)
raise NotAvailableValueError.new('#{disp_name}', new_value)
end
if [true, false].include?(new_value)
@#{name} = new_value
else
@#{name} = new_value == "true"
end
end
end
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}
@#{accessor_name}
end
def #{accessor_name}(*args)
if args.empty?
@#{accessor_name}.first
else
@#{accessor_name}[*args]
end
end
def #{accessor_name}=(*args)
receiver = self.class.name
warn("Warning:\#{caller.first.sub(/:in `.*'\z/, '')}: " \
"Don't use `\#{receiver}\##{accessor_name} = XXX'/" \
"`\#{receiver}\#set_#{accessor_name}(XXX)'. " \
"Those APIs are not sense of Ruby. " \
"Use `\#{receiver}\##{plural_name} << XXX' instead of them.")
if args.size == 1
@#{accessor_name}.push(args[0])
else
@#{accessor_name}.__send__("[]=", *args)
end
end
alias_method(:set_#{accessor_name}, :#{accessor_name}=)
EOC
end
end
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 = []
HAVE_CHILDREN_ELEMENTS = []
TO_ELEMENT_METHODS = []
NEED_INITIALIZE_VARIABLES = []
PLURAL_FORMS = {}
class << self
def must_call_validators
inherited_hash_reader("MUST_CALL_VALIDATORS")
end
def models
inherited_array_reader("MODELS")
end
def get_attributes
inherited_array_reader("GET_ATTRIBUTES")
end
def have_children_elements
inherited_array_reader("HAVE_CHILDREN_ELEMENTS")
end
def to_element_methods
inherited_array_reader("TO_ELEMENT_METHODS")
end
def need_initialize_variables
inherited_array_reader("NEED_INITIALIZE_VARIABLES")
end
def 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, {})
tag_name = klass.name.split(/::/).last
tag_name[0, 1] = tag_name[0, 1].downcase
klass.instance_variable_set(:@tag_name, tag_name)
klass.instance_variable_set(:@have_content, false)
end
def install_must_call_validator(prefix, uri)
self::MUST_CALL_VALIDATORS[uri] = prefix
end
def install_model(tag, uri, occurs=nil, getter=nil, plural=false)
getter ||= tag
if m = self::MODELS.find {|t, u, o, g, p| t == tag and u == uri}
m[2] = occurs
else
self::MODELS << [tag, uri, occurs, getter, plural]
end
end
def install_get_attribute(name, uri, required=true,
type=nil, disp_name=nil,
element_name=nil)
disp_name ||= name
element_name ||= name
writer_type, reader_type = type
def_corresponded_attr_writer name, writer_type, disp_name
def_corresponded_attr_reader name, reader_type
if type == :boolean and /^is/ =~ name
alias_method "#{$POSTMATCH}?", name
end
self::GET_ATTRIBUTES << [name, uri, required, element_name]
add_need_initialize_variable(disp_name)
end
def 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 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
end
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 have_content?
@have_content
end
def add_have_children_element(variable_name, plural_name)
self::HAVE_CHILDREN_ELEMENTS << [variable_name, plural_name]
end
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
nil
end
def required_uri
""
end
def need_parent?
false
end
def install_ns(prefix, uri)
if self::NSPOOL.has_key?(prefix)
raise OverlappedPrefixError.new(prefix)
end
self::NSPOOL[prefix] = uri
end
def tag_name
@tag_name
end
end
attr_accessor :parent, :do_validate
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 || {})
end
def tag_name
self.class.tag_name
end
def full_name
tag_name
end
def converter=(converter)
@converter = converter
targets = children.dup
self.class.have_children_elements.each do |variable_name, plural_name|
targets.concat(__send__(plural_name))
end
targets.each do |target|
target.converter = converter unless target.nil?
end
end
def convert(value)
if @converter
@converter.convert(value)
else
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 to_s(need_convert=true, indent='')
if self.class.have_content?
return "" if !empty_content? and !content_is_set?
rv = tag(indent) do |next_indent|
if empty_content?
""
else
xmled_content
end
end
else
rv = tag(indent) do |next_indent|
self.class.to_element_methods.collect do |method_name|
__send__(method_name, false, next_indent)
end
end
end
rv = convert(rv) if need_convert
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 = {}
attrs.each do |key, value|
normalized_attrs[key.to_s] = value
end
self.class.need_initialize_variables.each do |variable_name|
value = normalized_attrs[variable_name.to_s]
if value
__send__("#{variable_name}=", value)
else
instance_variable_set("@#{variable_name}", nil)
end
end
initialize_have_children_elements
@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_variable_set("@#{variable_name}", [])
end
end
def tag(indent, additional_attrs={}, &block)
next_indent = indent + INDENT
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.dup)
if block
content = block.call(next_indent)
else
content = []
end
if content.is_a?(String)
content = [content]
start_tag << ">"
end_tag = "</#{full_name}>"
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
def make_start_tag(indent, next_indent, attrs)
start_tag = ["#{indent}<#{full_name}"]
unless attrs.empty?
start_tag << attrs.collect do |key, value|
%Q[#{h key}="#{h value}"]
end.join("\n#{next_indent}")
end
start_tag.join(" ")
end
def collect_attrs
attrs = {}
_attrs.each do |name, required, alias_name|
value = __send__(alias_name || name)
return nil if required and value.nil?
next if value.nil?
return nil if attrs.has_key?(name)
attrs[name] = value
end
attrs
end
def tag_name_with_prefix(prefix)
"#{prefix}:#{tag_name}"
end
# For backward compatibility
def calc_indent
''
end
def children
rv = []
self.class.models.each do |name, uri, occurs, getter|
value = __send__(getter)
next if value.nil?
value = [value] unless value.is_a?(Array)
value.each do |v|
rv << v if v.is_a?(Element)
end
end
rv
end
def _tags
rv = []
self.class.models.each do |name, uri, occurs, getter, plural|
value = __send__(getter)
next if value.nil?
if plural and value.is_a?(Array)
rv.concat([[uri, name]] * value.size)
else
rv << [uri, name]
end
end
rv
end
def _attrs
self.class.get_attributes.collect do |name, uri, required, element_name|
[element_name, required, name]
end
end
def __validate(ignore_unknown_element, tags=_tags, recursive=true)
if recursive
children.compact.each do |child|
child.validate
end
end
must_call_validators = self.class.must_call_validators
tags = tag_filter(tags.dup)
p tags if DEBUG
must_call_validators.each do |uri, prefix|
_validate(ignore_unknown_element, tags[uri], uri)
meth = "#{prefix}_validate"
if !prefix.empty? and respond_to?(meth, true)
__send__(meth, ignore_unknown_element, tags[uri], uri)
end
end
end
def validate_attribute
_attrs.each do |a_name, required, alias_name|
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
def _validate(ignore_unknown_element, tags, uri, models=self.class.models)
count = 1
do_redo = false
not_shift = false
tag = nil
models = models.find_all {|model| model[1] == uri}
element_names = models.collect {|model| model[0]}
if tags
tags_size = tags.size
tags = tags.sort_by {|x| element_names.index(x) || tags_size}
end
models.each_with_index do |model, i|
name, _, occurs, = model
if DEBUG
p "before"
p tags
p model
end
if not_shift
not_shift = false
elsif tags
tag = tags.shift
end
if DEBUG
p "mid"
p count
end
case occurs
when '?'
if count > 2
raise TooMuchTagError.new(name, tag_name)
else
if name == tag
do_redo = true
else
not_shift = true
end
end
when '*'
if name == tag
do_redo = true
else
not_shift = true
end
when '+'
if name == tag
do_redo = true
else
if count > 1
not_shift = true
else
raise MissingTagError.new(name, tag_name)
end
end
else
if name == tag
if models[i+1] and models[i+1][0] != name and
tags and tags.first == name
raise TooMuchTagError.new(name, tag_name)
end
else
raise MissingTagError.new(name, tag_name)
end
end
if DEBUG
p "after"
p not_shift
p do_redo
p tag
end
if do_redo
do_redo = false
count += 1
redo
else
count = 1
end
end
if !ignore_unknown_element and !tags.nil? and !tags.empty?
raise NotExpectedTagError.new(tags.first, uri, tag_name)
end
end
def tag_filter(tags)
rv = {}
tags.each do |tag|
rv[tag[0]] = [] unless rv.has_key?(tag[0])
rv[tag[0]].push(tag[1])
end
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
attr_reader :feed_type, :feed_subtype, :feed_version
attr_accessor :version, :encoding, :standalone
def initialize(feed_version, version=nil, encoding=nil, standalone=nil)
super()
@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)
end
def setup_maker(maker)
maker.version = version
maker.encoding = encoding
maker.standalone = standalone
xml_stylesheets.each do |xss|
xss.setup_maker(maker)
end
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(type=nil, &block)
if type.nil? or same_feed_type?(type)
to_s
else
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 = super(indent, ns_declarations.merge(attrs), &block)
return rv if rv.empty?
"#{xmldecl}#{xml_stylesheet_pi}#{rv}"
end
def xmldecl
rv = %Q[<?xml version="#{@version}"]
if @output_encoding or @encoding
rv << %Q[ encoding="#{@output_encoding or @encoding}"]
end
rv << %Q[ standalone="yes"] if @standalone
rv << "?>\n"
rv
end
def ns_declarations
decls = {}
self.class::NSPOOL.collect do |prefix, uri|
prefix = ":#{prefix}" unless prefix.empty?
decls["xmlns#{prefix}"] = uri
end
decls
end
def maker_target(target)
target
end
end
end