diff options
author | Jari Vetoniemi <jari.vetoniemi@indooratlas.com> | 2020-03-16 18:49:26 +0900 |
---|---|---|
committer | Jari Vetoniemi <jari.vetoniemi@indooratlas.com> | 2020-03-30 00:39:06 +0900 |
commit | fcbf63e62c627deae76c1b8cb8c0876c536ed811 (patch) | |
tree | 64cb17de3f41a2b6fef2368028fbd00349946994 /jni/ruby/lib/rss/rss.rb |
Fresh start
Diffstat (limited to 'jni/ruby/lib/rss/rss.rb')
-rw-r--r-- | jni/ruby/lib/rss/rss.rb | 1352 |
1 files changed, 1352 insertions, 0 deletions
diff --git a/jni/ruby/lib/rss/rss.rb b/jni/ruby/lib/rss/rss.rb new file mode 100644 index 0000000..4f6732b --- /dev/null +++ b/jni/ruby/lib/rss/rss.rb @@ -0,0 +1,1352 @@ +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 = Math.log10(usec.to_s.sub(/0*$/, '').to_i).floor + 1 + 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| + attr = attr.id2name if attr.kind_of?(Integer) + module_eval(<<-EOC, *get_file_and_line_from_caller(2)) + def #{attr}_without_inherit + convert(@#{attr}) + end + + def #{attr} + if @#{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} + value = #{attr}_without_base + return nil if value.nil? + if /\\A[a-z][a-z0-9+.\\-]*:/i =~ value + value + else + "\#{base}\#{value}" + end + end +EOC + end + end + + def convert_attr_reader(*attrs) + attrs.each do |attr| + attr = attr.id2name if attr.kind_of?(Integer) + module_eval(<<-EOC, *get_file_and_line_from_caller(2)) + def #{attr} + convert(@#{attr}) + end +EOC + end + end + + def yes_clean_other_attr_reader(*attrs) + attrs.each do |attr| + attr = attr.id2name if attr.kind_of?(Integer) + module_eval(<<-EOC, __FILE__, __LINE__ + 1) + attr_reader(:#{attr}) + def #{attr}? + YesCleanOther.parse(@#{attr}) + end + EOC + end + end + + def yes_other_attr_reader(*attrs) + attrs.each do |attr| + attr = attr.id2name if attr.kind_of?(Integer) + module_eval(<<-EOC, __FILE__, __LINE__ + 1) + attr_reader(:#{attr}) + def #{attr}? + Utils::YesOther.parse(@#{attr}) + end + EOC + end + end + + def csv_attr_reader(*attrs) + separator = nil + if attrs.last.is_a?(Hash) + options = attrs.pop + separator = options[:separator] + end + separator ||= ", " + attrs.each do |attr| + attr = attr.id2name if attr.kind_of?(Integer) + module_eval(<<-EOC, __FILE__, __LINE__ + 1) + attr_reader(:#{attr}) + def #{attr}_content + if @#{attr}.nil? + @#{attr} + else + @#{attr}.join(#{separator.dump}) + end + end + EOC + end + end + + def date_writer(name, type, disp_name=name) + module_eval(<<-EOC, *get_file_and_line_from_caller(2)) + def #{name}=(new_value) + if new_value.nil? + @#{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 yes_clean_other_writer(name, disp_name=name) + module_eval(<<-EOC, __FILE__, __LINE__ + 1) + def #{name}=(value) + value = (value ? "yes" : "no") if [true, false].include?(value) + @#{name} = value + end + EOC + end + + def yes_other_writer(name, disp_name=name) + module_eval(<<-EOC, __FILE__, __LINE__ + 1) + def #{name}=(new_value) + if [true, false].include?(new_value) + new_value = new_value ? "yes" : "no" + end + @#{name} = new_value + end + EOC + end + + def csv_writer(name, disp_name=name) + module_eval(<<-EOC, __FILE__, __LINE__ + 1) + def #{name}=(new_value) + @#{name} = Utils::CSV.parse(new_value) + end + EOC + end + + def csv_integer_writer(name, disp_name=name) + module_eval(<<-EOC, __FILE__, __LINE__ + 1) + def #{name}=(new_value) + @#{name} = Utils::CSV.parse(new_value) {|v| Integer(v)} + end + EOC + end + + def def_children_accessor(accessor_name, plural_name) + module_eval(<<-EOC, *get_file_and_line_from_caller(2)) + def #{plural_name} + @#{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 :yes_clean_other + yes_clean_other_writer name, disp_name + when :yes_other + yes_other_writer name, disp_name + when :csv + csv_writer name + when :csv_integer + csv_integer_writer name + else + attr_writer name + end + end + + def def_corresponded_attr_reader(name, type=nil) + case type + when :inherit + inherit_convert_attr_reader name + when :uri + uri_convert_attr_reader name + when :yes_clean_other + yes_clean_other_attr_reader name + when :yes_other + yes_other_attr_reader name + when :csv + csv_attr_reader name + when :csv_integer + csv_attr_reader name, :separator => "," + else + convert_attr_reader name + 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 + !child.to_s.empty? + 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("m").delete("\n") 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 |