diff options
author | (no author) <(no author)@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2004-04-06 08:02:56 +0000 |
---|---|---|
committer | (no author) <(no author)@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2004-04-06 08:02:56 +0000 |
commit | 5923a2c0e770515a9c7144c50e09a32350695b00 (patch) | |
tree | 66882c2ac60d7d1b25bb49ee476cb829695aa906 /lib/rss | |
parent | 8e773df6d3b68caf2e56e6c75a8e48bf2ccc1bd3 (diff) |
This commit was manufactured by cvs2svn to create branch 'ruby_1_8'.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/branches/ruby_1_8@6109 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/rss')
-rw-r--r-- | lib/rss/0.9.rb | 409 | ||||
-rw-r--r-- | lib/rss/2.0.rb | 147 | ||||
-rw-r--r-- | lib/rss/converter.rb | 154 | ||||
-rw-r--r-- | lib/rss/syndication.rb | 83 | ||||
-rw-r--r-- | lib/rss/taxonomy.rb | 32 | ||||
-rw-r--r-- | lib/rss/utils.rb | 17 | ||||
-rw-r--r-- | lib/rss/xml-stylesheet.rb | 94 | ||||
-rw-r--r-- | lib/rss/xmlscanner.rb | 102 |
8 files changed, 1038 insertions, 0 deletions
diff --git a/lib/rss/0.9.rb b/lib/rss/0.9.rb new file mode 100644 index 0000000000..bb3cc23beb --- /dev/null +++ b/lib/rss/0.9.rb @@ -0,0 +1,409 @@ +require "rss/parser" + +module RSS + + module RSS09 + NSPOOL = {} + ELEMENTS = [] + end + + class Rss < Element + + include RSS09 + include RootElementMixin + include XMLStyleSheetMixin + + [ + ["channel", nil], + ].each do |tag, occurs| + install_model(tag, occurs) + end + + %w(channel).each do |x| + install_have_child_element(x) + end + + attr_accessor :rss_version, :version, :encoding, :standalone + + def initialize(rss_version, version=nil, encoding=nil, standalone=nil) + super + end + + def items + if @channel + @channel.items + else + [] + end + end + + def image + if @channel + @channel.image + else + nil + end + end + + def to_s(convert=true) + rv = <<-EOR +#{xmldecl} +#{xml_stylesheet_pi}<rss version="#{@rss_version}"#{ns_declaration}> +#{channel_element(false)} +#{other_element(false, "\t")} +</rss> +EOR + rv = @converter.convert(rv) if convert and @converter + rv + end + + private + def children + [@channel] + end + + class Channel < Element + + include RSS09 + + [ + ["title", nil], + ["link", nil], + ["description", nil], + ["language", nil], + ["copyright", "?"], + ["managingEditor", "?"], + ["webMaster", "?"], + ["rating", "?"], + ["docs", "?"], + ["skipDays", "?"], + ["skipHours", "?"], + ].each do |x, occurs| + install_text_element(x) + install_model(x, occurs) + end + + [ + ["pubDate", "?"], + ["lastBuildDate", "?"], + ].each do |x, occurs| + install_date_element(x, 'rfc822') + install_model(x, occurs) + end + + [ + ["image", nil], + ["textInput", "?"], + ["cloud", "?"] + ].each do |x, occurs| + install_have_child_element(x) + install_model(x, occurs) + end + + [ + ["item", "*"] + ].each do |x, occurs| + install_have_children_element(x) + install_model(x, occurs) + end + + def initialize() + super() + end + + def to_s(convert=true) + rv = <<-EOT + <channel> + #{title_element(false)} + #{link_element(false)} + #{description_element(false)} + #{language_element(false)} + #{copyright_element(false)} + #{managingEditor_element(false)} + #{webMaster_element(false)} + #{rating_element(false)} + #{pubDate_element(false)} + #{lastBuildDate_element(false)} + #{docs_element(false)} + #{skipDays_element(false)} + #{skipHours_element(false)} + #{image_element(false)} +#{item_elements(false)} + #{textInput_element(false)} +#{other_element(false, "\t\t")} + </channel> +EOT + rv = @converter.convert(rv) if convert and @converter + rv + end + + private + def children + [@image, @textInput, @cloud, *@item] + end + + class Image < Element + + include RSS09 + + %w(url title link width height description).each do |x| + install_text_element(x) + end + + def to_s(convert=true) + rv = <<-EOT + <image> + #{url_element(false)} + #{title_element(false)} + #{link_element(false)} + #{width_element(false)} + #{height_element(false)} + #{description_element(false)} +#{other_element(false, "\t\t\t\t")} + </image> +EOT + rv = @converter.convert(rv) if convert and @converter + rv + end + + end + + class Cloud < Element + + include RSS09 + + [ + ["domain", nil, false], + ["port", nil, false], + ["path", nil, false], + ["registerProcedure", nil, false], + ["protocol", nil ,false], + ].each do |name, uri, required| + install_get_attribute(name, uri, required) + end + + def to_s(convert=true) + rv = <<-EOT + <cloud + domain="#{h @domain}" + port="#{h @port}" + path="#{h @path}" + registerProcedure="#{h @registerProcedure}" + protocol="#{h @protocol}"/> +EOT + rv = @converter.convert(rv) if convert and @converter + rv + end + + end + + class Item < Element + + include RSS09 + + %w(title link description author comments).each do |x| + install_text_element(x) + end + + %w(category source enclosure).each do |x| + install_have_child_element(x) + end + + [ + ["title", '?'], + ["link", '?'], + ["description", '?'], + ["author", '?'], + ["comments", '?'], + ["category", '?'], + ["source", '?'], + ["enclosure", '?'], + ].each do |tag, occurs| + install_model(tag, occurs) + end + + def to_s(convert=true) + rv = <<-EOT + <item> + #{title_element(false)} + #{link_element(false)} + #{description_element(false)} + #{author_element(false)} + #{category_element(false)} + #{comments_element(false)} + #{enclosure_element(false)} + #{source_element(false)} +#{other_element(false, "\t\t\t\t")} + </item> +EOT + rv = @converter.convert(rv) if convert and @converter + rv + end + + class Source < Element + + include RSS09 + + [ + ["url", nil, true] + ].each do |name, uri, required| + install_get_attribute(name, uri, required) + end + + content_setup + + def initialize(url=nil, content=nil) + super() + @url = url + @content = content + end + + def to_s(convert=true) + if @url + rv = %Q! <source url="#{@url}">! + rv << %Q!#{@content}</source>! + rv = @converter.convert(rv) if convert and @converter + rv + else + '' + end + end + + private + def _attrs + [ + ["url", true] + ] + end + + end + + class Enclosure < Element + + include RSS09 + + [ + ["url", nil, true], + ["length", nil, true], + ["type", nil, true], + ].each do |name, uri, required| + install_get_attribute(name, uri, required) + end + + def initialize(url=nil, length=nil, type=nil) + super() + @url = url + @length = length + @type = type + end + + def to_s(convert=true) + if @url and @length and @type + rv = %Q!<enclosure url="#{h @url}" ! + rv << %Q!length="#{h @length}" type="#{h @type}"/>! + rv = @converter.convert(rv) if convert and @converter + rv + else + '' + end + end + + private + def _attrs + [ + ["url", true], + ["length", true], + ["type", true], + ] + end + + end + + class Category < Element + + include RSS09 + + [ + ["domain", nil, true] + ].each do |name, uri, required| + install_get_attribute(name, uri, required) + end + + content_setup + + def initialize(domain=nil, content=nil) + super() + @domain = domain + @content = content + end + + def to_s(convert=true) + if @domain + rv = %Q!<category domain="#{h @domain}">! + rv << %Q!#{h @content}</category>! + rv = @converter.convert(rv) if convert and @converter + rv + else + '' + end + end + + private + def _attrs + [ + ["domain", true] + ] + end + + end + + end + + class TextInput < Element + + include RSS09 + + %w(title description name link).each do |x| + install_text_element(x) + end + + def to_s(convert=true) + rv = <<-EOT + <textInput> + #{title_element(false)} + #{description_element(false)} + #{name_element(false)} + #{link_element(false)} +#{other_element(false, "\t\t\t\t")} + </textInput> +EOT + rv = @converter.convert(rv) if convert and @converter + rv + end + + end + + end + + end + + RSS09::ELEMENTS.each do |x| + BaseListener.install_get_text_element(x, nil, "#{x}=") + end + + module ListenerMixin + private + def start_rss(tag_name, prefix, attrs, ns) + check_ns(tag_name, prefix, ns, nil) + + @rss = Rss.new(attrs['version'], @version, @encoding, @standalone) + @rss.xml_stylesheets = @xml_stylesheets + @last_element = @rss + @proc_stack.push Proc.new { |text, tags| + @rss.validate_for_stream(tags) if @do_validate + } + end + + end + +end diff --git a/lib/rss/2.0.rb b/lib/rss/2.0.rb new file mode 100644 index 0000000000..c83fb2c393 --- /dev/null +++ b/lib/rss/2.0.rb @@ -0,0 +1,147 @@ +require "rss/0.9" + +module RSS + + class Rss + +# URI = "http://backend.userland.com/rss2" + +# install_ns('', URI) + +# def self.required_uri +# URI +# end + + class Channel + +# def self.required_uri +# URI +# end + + %w(generator ttl).each do |x| + install_text_element(x) + end + + %w(category).each do |x| + install_have_child_element(x) + end + + [ + ["image", "?"], + ].each do |x, occurs| + install_model(x, occurs) + end + + def other_element(convert, indent='') + rv = <<-EOT +#{indent}#{category_element(convert)} +#{indent}#{generator_element(convert)} +#{indent}#{ttl_element(convert)} +EOT + rv << super + end + + Category = Item::Category +# def Category.required_uri +# URI +# end + + class Item + +# def self.required_uri +# URI +# end + + [ + ["pubDate", '?'], + ].each do |x, occurs| + install_date_element(x, 'rfc822') + install_model(x, occurs) + end + + [ + ["guid", '?'], + ].each do |x, occurs| + install_have_child_element(x) + install_model(x, occurs) + end + + def other_element(convert, indent='') + rv = <<-EOT +#{indent}#{pubDate_element(false)} +#{indent}#{guid_element(false)} +EOT + rv << super + end + + class Guid < Element + + include RSS09 + +# def self.required_uri +# URI +# end + + [ + ["isPermaLink", nil, false] + ].each do |name, uri, required| + install_get_attribute(name, uri, required) + end + + content_setup + + def initialize(isPermaLink=nil, content=nil) + super() + @isPermaLink = isPermaLink + @content = content + end + + def to_s(convert=true) + if @content + rv = %Q!<guid! + rv << %Q! isPermaLink="#{h @isPermaLink}"! if @isPermaLink + rv << %Q!>#{h @content}</guid>! + rv = @converter.convert(rv) if convert and @converter + rv + else + '' + end + end + + private + def _attrs + [ + ["isPermaLink", false] + ] + end + + end + + end + + end + + end + + RSS09::ELEMENTS.each do |x| +# BaseListener.install_get_text_element(x, Rss::URI, "#{x}=") + BaseListener.install_get_text_element(x, nil, "#{x}=") + end + + module ListenerMixin + private + alias start_rss09 start_rss + def start_rss(tag_name, prefix, attrs, ns) +# check_ns(tag_name, prefix, ns, Rss::URI) + + @rss = Rss.new(attrs['version'], @version, @encoding, @standalone) + @rss.xml_stylesheets = @xml_stylesheets + @last_element = @rss + @proc_stack.push Proc.new { |text, tags| + @rss.validate_for_stream(tags) if @do_validate + } + end + + end + +end diff --git a/lib/rss/converter.rb b/lib/rss/converter.rb new file mode 100644 index 0000000000..9a62431a9e --- /dev/null +++ b/lib/rss/converter.rb @@ -0,0 +1,154 @@ +require "rss/utils" + +module RSS + + class Converter + + include Utils + + def initialize(to_enc, from_enc=nil) + to_enc = to_enc.downcase.gsub(/-/, '_') + from_enc ||= 'utf-8' + from_enc = from_enc.downcase.gsub(/-/, '_') + if to_enc == from_enc + def_same_enc() + else + if respond_to?("def_to_#{to_enc}_from_#{from_enc}") + send("def_to_#{to_enc}_from_#{from_enc}") + else + def_else_enc(to_enc, from_enc) + end + end + end + + def convert(value) + value + end + + def def_convert(depth=0) + instance_eval(<<-EOC, *get_file_and_line_from_caller(depth)) + def convert(value) + if value.kind_of?(String) + #{yield('value')} + else + value + end + end + EOC + end + + def def_iconv_convert(to_enc, from_enc, depth=0) + begin + require "iconv" + def_convert(depth+1) do |value| + <<-EOC + @iconv ||= Iconv.new("#{to_enc}", "#{from_enc}") + begin + @iconv.iconv(#{value}) + rescue Iconv::Failure + raise ConversionError.new(#{value}, "#{to_enc}", "#{from_enc}") + end + EOC + end + rescue LoadError, ArgumentError, SystemCallError + raise UnknownConversionMethodError.new(to_enc, from_enc) + end + end + + def def_else_enc(to_enc, from_enc) + raise UnknownConversionMethodError.new(to_enc, from_enc) + end + + def def_same_enc() + def_convert do |value| + value + end + end + + def def_uconv_convert_if_can(meth, to_enc, from_enc) + begin + require "uconv" + def_convert(1) do |value| + <<-EOC + begin + Uconv.#{meth}(#{value}) + rescue Uconv::Error + raise ConversionError.new(#{value}, "#{to_enc}", "#{from_enc}") + end + EOC + end + rescue LoadError + def_iconv_convert(to_enc, from_enc, 1) + end + end + + def def_to_euc_jp_from_utf_8 + def_uconv_convert_if_can('u8toeuc', 'EUC-JP', 'UTF-8') + end + + def def_to_utf_8_from_euc_jp + def_uconv_convert_if_can('euctou8', 'UTF-8', 'EUC-JP') + end + + def def_to_shift_jis_from_utf_8 + def_uconv_convert_if_can('u8tosjis', 'Shift_JIS', 'UTF-8') + end + + def def_to_utf_8_from_shift_jis + def_uconv_convert_if_can('sjistou8', 'UTF-8', 'Shift_JIS') + end + + def def_to_euc_jp_from_shift_jis + require "nkf" + def_convert do |value| + "NKF.nkf('-Se', #{value})" + end + end + + def def_to_shift_jis_from_euc_jp + require "nkf" + def_convert do |value| + "NKF.nkf('-Es', #{value})" + end + end + + def def_to_euc_jp_from_iso_2022_jp + require "nkf" + def_convert do |value| + "NKF.nkf('-Je', #{value})" + end + end + + def def_to_iso_2022_jp_from_euc_jp + require "nkf" + def_convert do |value| + "NKF.nkf('-Ej', #{value})" + end + end + + def def_to_utf_8_from_iso_8859_1 + def_convert do |value| + "#{value}.unpack('C*').pack('U*')" + end + end + + def def_to_iso_8859_1_from_utf_8 + def_convert do |value| + <<-EOC + array_utf8 = #{value}.unpack('U*') + array_enc = [] + array_utf8.each do |num| + if num <= 0xFF + array_enc << num + else + array_enc.concat "&\#\#{num};".unpack('C*') + end + end + array_enc.pack('C*') + EOC + end + end + + end + +end diff --git a/lib/rss/syndication.rb b/lib/rss/syndication.rb new file mode 100644 index 0000000000..8c10688b57 --- /dev/null +++ b/lib/rss/syndication.rb @@ -0,0 +1,83 @@ +require "rss/1.0" + +module RSS + + SY_PREFIX = 'sy' + SY_URI = "http://purl.org/rss/1.0/modules/syndication/" + + RDF.install_ns(SY_PREFIX, SY_URI) + + module SyndicationModel + + extend BaseModel + + ELEMENTS = [] + + def self.included(mod) + mod.module_eval(<<-EOC) + %w(updatePeriod updateFrequency).each do |x| + install_text_element("\#{SY_PREFIX}_\#{x}") + end + + %w(updateBase).each do |x| + install_date_element("\#{SY_PREFIX}_\#{x}", 'w3cdtf', x) + end + + alias_method(:_sy_updatePeriod=, :sy_updatePeriod=) + def sy_updatePeriod=(new_value) + new_value = new_value.strip + validate_sy_updatePeriod(new_value) if @do_validate + self._sy_updatePeriod = new_value + end + + alias_method(:_sy_updateFrequency=, :sy_updateFrequency=) + def sy_updateFrequency=(new_value) + new_value = new_value.strip + validate_sy_updateFrequency(new_value) if @do_validate + self._sy_updateFrequency = new_value.to_i + end + EOC + end + + def sy_validate(tags) + counter = {} + ELEMENTS.each do |x| + counter[x] = 0 + end + + tags.each do |tag| + key = "#{SY_PREFIX}_#{tag}" + raise UnknownTagError.new(tag, SY_URI) unless counter.has_key?(key) + counter[key] += 1 + raise TooMuchTagError.new(tag, tag_name) if counter[key] > 1 + end + end + + private + SY_UPDATEPERIOD_AVAILABLE_VALUES = %w(hourly daily weekly monthly yearly) + def validate_sy_updatePeriod(value) + unless SY_UPDATEPERIOD_AVAILABLE_VALUES.include?(value) + raise NotAvailableValueError.new("updatePeriod", value) + end + end + + SY_UPDATEFREQUENCY_AVAILABLE_RE = /\A\s*\+?\d+\s*\z/ + def validate_sy_updateFrequency(value) + if SY_UPDATEFREQUENCY_AVAILABLE_RE !~ value + raise NotAvailableValueError.new("updateFrequency", value) + end + end + + end + + class RDF + class Channel; include SyndicationModel; end + end + + prefix_size = SY_PREFIX.size + 1 + SyndicationModel::ELEMENTS.uniq! + SyndicationModel::ELEMENTS.each do |x| + BaseListener.install_get_text_element(x[prefix_size..-1], SY_URI, "#{x}=") + end + +end diff --git a/lib/rss/taxonomy.rb b/lib/rss/taxonomy.rb new file mode 100644 index 0000000000..5b11d338e0 --- /dev/null +++ b/lib/rss/taxonomy.rb @@ -0,0 +1,32 @@ +# Experimental + +require "rss/1.0" + +module RSS + + TAXO_PREFIX = "taxo" + TAXO_NS = "http://purl.org/rss/1.0/modules/taxonomy/" + + Element.install_ns(TAXO_PREFIX, TAXO_NS) + + TAXO_ELEMENTS = [] + + %w(link).each do |x| + if const_defined? :Listener + Listener.install_get_text_element(x, TAXO_NS, "#{TAXO_PREFIX}_#{x}=") + end + TAXO_ELEMENTS << "#{TAXO_PREFIX}_#{x}" + end + + module TaxonomyModel + attr_writer(*%w(title description creator subject publisher + contributor date format identifier source + language relation coverage rights).collect{|x| "#{TAXO_PREFIX}_#{x}"}) + end + + class Channel; extend TaxonomyModel; end + class Item; extend TaxonomyModel; end + class Image; extend TaxonomyModel; end + class TextInput; extend TaxonomyModel; end + +end diff --git a/lib/rss/utils.rb b/lib/rss/utils.rb new file mode 100644 index 0000000000..ae6f69bcf1 --- /dev/null +++ b/lib/rss/utils.rb @@ -0,0 +1,17 @@ +module RSS + + module Utils + + def get_file_and_line_from_caller(i=0) + file, line, = caller[i].split(':') + [file, line.to_i] + end + + def html_escape(s) + s.to_s.gsub(/&/, "&").gsub(/\"/, """).gsub(/>/, ">").gsub(/</, "<") + end + alias h html_escape + + end + +end diff --git a/lib/rss/xml-stylesheet.rb b/lib/rss/xml-stylesheet.rb new file mode 100644 index 0000000000..7862c4f278 --- /dev/null +++ b/lib/rss/xml-stylesheet.rb @@ -0,0 +1,94 @@ +require "rss/utils" + +module RSS + + module XMLStyleSheetMixin + attr_accessor :xml_stylesheets + def initialize(*args) + super + @xml_stylesheets = [] + end + + private + def xml_stylesheet_pi + xsss = @xml_stylesheets.collect do |xss| + pi = xss.to_s + pi = nil if /\A\s*\z/ =~ pi + pi + end.compact + xsss.push("") unless xsss.empty? + xsss.join("\n") + end + end + + class XMLStyleSheet + + include Utils + + ATTRIBUTES = %w(href type title media charset alternate) + + GUESS_TABLE = { + "xsl" => "text/xsl", + "css" => "text/css", + } + + attr_accessor(*ATTRIBUTES) + attr_accessor(:do_validate) + def initialize(*attrs) + @do_validate = true + ATTRIBUTES.each do |attr| + self.send("#{attr}=", nil) + end + vars = ATTRIBUTES.dup + vars.unshift(:do_validate) + attrs.each do |name, value| + if vars.include?(name.to_s) + self.send("#{name}=", value) + end + end + end + + def to_s + rv = "" + if @href + rv << %Q[<?xml-stylesheet] + ATTRIBUTES.each do |name| + if self.send(name) + rv << %Q[ #{name}="#{h self.send(name)}"] + end + end + rv << %Q[?>] + end + rv + end + + remove_method(:href=) + def href=(value) + @href = value + if @href and @type.nil? + @type = guess_type(@href) + end + @href + end + + remove_method(:alternate=) + def alternate=(value) + if value.nil? or /\A(?:yes|no)\z/ =~ value + @alternate = value + else + if @do_validate + args = ["?xml-stylesheet?", %Q[alternate="#{value}"]] + raise NotAvailableValueError.new(*args) + end + end + @alternate + end + + private + def guess_type(filename) + /\.([^.]+)/ =~ filename + GUESS_TABLE[$1] + end + + end +end diff --git a/lib/rss/xmlscanner.rb b/lib/rss/xmlscanner.rb new file mode 100644 index 0000000000..4ab997062d --- /dev/null +++ b/lib/rss/xmlscanner.rb @@ -0,0 +1,102 @@ +require 'xmlscan/scanner' + +module RSS + + class XMLScanParser < BaseParser + + private + def listener + XMLScanListener + end + + def _parse + begin + XMLScan::XMLScanner.new(@listener).parse(@rss) + rescue XMLScan::Error => e + raise NotWellFormedError.new(e.lineno){e.message} + end + end + + end + + class XMLScanListener < BaseListener + + include XMLScan::Visitor + include ListenerMixin + + ENTITIES = { + 'lt' => '<', + 'gt' => '>', + 'amp' => '&', + 'quot' => '"', + 'apos' => '\'' + } + + def on_xmldecl_version(str) + @version = str + end + + def on_xmldecl_encoding(str) + @encoding = str + end + + def on_xmldecl_standalone(str) + @standalone = str + end + + def on_xmldecl_end + xmldecl(@version, @encoding, @standalone) + end + + alias_method(:on_pi, :instruction) + alias_method(:on_chardata, :text) + alias_method(:on_cdata, :text) + + def on_etag(name) + tag_end(name) + end + + def on_entityref(ref) + text(ENTITIES[ref]) + end + + def on_charref(code) + text([code].pack('U')) + end + + alias_method(:on_charref_hex, :on_charref) + + def on_stag(name) + @attrs = {} + end + + def on_attribute(name) + @attrs[name] = @current_attr = '' + end + + def on_attr_value(str) + @current_attr << str + end + + def on_attr_entityref(ref) + @current_attr << ENTITIES[ref] + end + + def on_attr_charref(code) + @current_attr << [code].pack('U') + end + + alias_method(:on_attr_charref_hex, :on_attr_charref) + + def on_stag_end(name) + tag_start(name, @attrs) + end + + def on_stag_end_empty(name) + tag_start(name, @attrs) + tag_end(name) + end + + end + +end |