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) time.localtime unless zone_utc?(zone) 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 << "" 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 << "" 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 = "" else content = content.reject{|x| x.empty?} if content.empty? return "" if attrs.empty? end_tag = "/>" else start_tag << ">\n" end_tag = "\n#{indent}" 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[\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