diff options
-rw-r--r-- | ChangeLog | 7 | ||||
-rw-r--r-- | lib/rss/0.9.rb | 35 | ||||
-rw-r--r-- | lib/rss/1.0.rb | 50 | ||||
-rw-r--r-- | lib/rss/2.0.rb | 1 | ||||
-rw-r--r-- | lib/rss/converter.rb | 12 | ||||
-rw-r--r-- | lib/rss/parser.rb | 28 | ||||
-rw-r--r-- | lib/rss/rss.rb | 43 | ||||
-rw-r--r-- | lib/rss/utils.rb | 6 | ||||
-rw-r--r-- | lib/rss/xml-stylesheet.rb | 94 | ||||
-rw-r--r-- | lib/rss/xmlparser.rb | 14 | ||||
-rw-r--r-- | lib/rss/xmlscanner.rb | 1 | ||||
-rw-r--r-- | test/rss/common.rb | 9 | ||||
-rw-r--r-- | test/rss/my-assertions.rb | 39 | ||||
-rw-r--r-- | test/rss/test_1.0.rb | 22 | ||||
-rw-r--r-- | test/rss/test_xml-stylesheet.rb | 109 |
15 files changed, 385 insertions, 85 deletions
@@ -1,3 +1,10 @@ +Sun Mar 21 18:15:29 2004 Kouhei Sutou <kou@cozmixng.org> + + * test/rss/test_xml-stylesheet.rb: added tests for xml-stylesheet. + + * lib/rss/xml-stylesheet.rb: added xml-stylesheet parsing + function. + Sat Mar 20 23:51:03 2004 WATANABE Hirofumi <eban@ruby-lang.org> * eval.c (rb_require_safe): preserve old ruby_errinfo. diff --git a/lib/rss/0.9.rb b/lib/rss/0.9.rb index 4f03333d96..bb3cc23beb 100644 --- a/lib/rss/0.9.rb +++ b/lib/rss/0.9.rb @@ -10,6 +10,8 @@ module RSS class Rss < Element include RSS09 + include RootElementMixin + include XMLStyleSheetMixin [ ["channel", nil], @@ -24,18 +26,9 @@ module RSS attr_accessor :rss_version, :version, :encoding, :standalone def initialize(rss_version, version=nil, encoding=nil, standalone=nil) - super() - @rss_version = rss_version - @version = version || '1.0' - @encoding = encoding - @standalone = standalone + super end - def output_encoding=(enc) - @output_encoding = enc - self.converter = Converter.new(@output_encoding, @encoding) - end - def items if @channel @channel.items @@ -55,7 +48,7 @@ module RSS def to_s(convert=true) rv = <<-EOR #{xmldecl} -<rss version="#{@rss_version}"#{ns_declaration}> +#{xml_stylesheet_pi}<rss version="#{@rss_version}"#{ns_declaration}> #{channel_element(false)} #{other_element(false, "\t")} </rss> @@ -65,25 +58,6 @@ EOR end private - def xmldecl - rv = "<?xml version='#{@version}'" - if @output_encoding or @encoding - rv << " encoding='#{@output_encoding or @encoding}'" - end - rv << " standalone='#{@standalone}'" if @standalone - rv << '?>' - rv - end - - def ns_declaration - rv = '' - NSPOOL.each do |prefix, uri| - prefix = ":#{prefix}" unless prefix.empty? - rv << %Q|\n\txmlns#{prefix}="#{uri}"| - end - rv - end - def children [@channel] end @@ -423,6 +397,7 @@ EOT 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 diff --git a/lib/rss/1.0.rb b/lib/rss/1.0.rb index cd645eedb3..6c33f1695d 100644 --- a/lib/rss/1.0.rb +++ b/lib/rss/1.0.rb @@ -10,6 +10,8 @@ module RSS class RDF < Element include RSS10 + include RootElementMixin + include XMLStyleSheetMixin class << self @@ -45,23 +47,13 @@ module RSS attr_accessor :rss_version, :version, :encoding, :standalone def initialize(version=nil, encoding=nil, standalone=nil) - super() - @rss_version = '1.0' - @version = version || '1.0' - @encoding = encoding - @standalone = standalone - @output_encoding = nil - end - - def output_encoding=(enc) - @output_encoding = enc - self.converter = Converter.new(@output_encoding, @encoding) + super('1.0', version, encoding, standalone) end def to_s(convert=true) rv = <<-EORDF #{xmldecl} -<#{PREFIX}:RDF#{ns_declaration}> +#{xml_stylesheet_pi}<#{PREFIX}:RDF#{ns_declaration}> #{channel_element(false)} #{image_element(false)} #{item_elements(false)} @@ -74,25 +66,6 @@ EORDF end private - def xmldecl - rv = %Q[<?xml version="#{@version}"] - if @output_encoding or @encoding - rv << %Q[ encoding="#{@output_encoding or @encoding}"] - end - rv << %Q[ standalone="#{@standalone}"] if @standalone - rv << '?>' - rv - end - - def ns_declaration - rv = '' - self.class::NSPOOL.each do |prefix, uri| - prefix = ":#{prefix}" unless prefix.empty? - rv << %Q|\n\txmlns#{prefix}="#{html_escape(uri)}"| - end - rv - end - def rdf_validate(tags) _validate(tags, []) end @@ -647,4 +620,19 @@ EOT BaseListener.install_get_text_element(x, URI, "#{x}=") end + module ListenerMixin + private + def start_RDF(tag_name, prefix, attrs, ns) + check_ns(tag_name, prefix, ns, RDF::URI) + + @rss = RDF.new(@version, @encoding, @standalone) + @rss.do_validate = @do_validate + @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 index de48c20e3c..c83fb2c393 100644 --- a/lib/rss/2.0.rb +++ b/lib/rss/2.0.rb @@ -135,6 +135,7 @@ EOT # 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 diff --git a/lib/rss/converter.rb b/lib/rss/converter.rb index 5037606f37..9a62431a9e 100644 --- a/lib/rss/converter.rb +++ b/lib/rss/converter.rb @@ -25,8 +25,8 @@ module RSS value end - def def_convert() - instance_eval(<<-EOC, *get_file_and_line_from_caller(0)) + 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')} @@ -37,10 +37,10 @@ module RSS EOC end - def def_iconv_convert(to_enc, from_enc) + def def_iconv_convert(to_enc, from_enc, depth=0) begin require "iconv" - def_convert do |value| + def_convert(depth+1) do |value| <<-EOC @iconv ||= Iconv.new("#{to_enc}", "#{from_enc}") begin @@ -68,7 +68,7 @@ module RSS def def_uconv_convert_if_can(meth, to_enc, from_enc) begin require "uconv" - def_convert do |value| + def_convert(1) do |value| <<-EOC begin Uconv.#{meth}(#{value}) @@ -78,7 +78,7 @@ module RSS EOC end rescue LoadError - def_iconv_convert(to_enc, from_enc) + def_iconv_convert(to_enc, from_enc, 1) end end diff --git a/lib/rss/parser.rb b/lib/rss/parser.rb index aadd9658cf..a04f7959ec 100644 --- a/lib/rss/parser.rb +++ b/lib/rss/parser.rb @@ -191,12 +191,22 @@ module RSS @proc_stack = [] @last_element = nil @version = @encoding = @standalone = nil + @xml_stylesheets = [] end def xmldecl(version, encoding, standalone) @version, @encoding, @standalone = version, encoding, standalone end + def instruction(name, content) + if name == "xml-stylesheet" + params = parse_pi_content(content) + if params.has_key?("href") + @xml_stylesheets << XMLStyleSheet.new(*params) + end + end + end + def tag_start(name, attributes) @text_stack.push('') @@ -204,7 +214,7 @@ module RSS attrs = {} attributes.each do |n, v| if n =~ /\Axmlns:?/ - ns[$'] = v # $' is post match + ns[$POSTMATCH] = v else attrs[n] = v end @@ -238,15 +248,13 @@ module RSS private - def start_RDF(tag_name, prefix, attrs, ns) - check_ns(tag_name, prefix, ns, RDF::URI) - - @rss = RDF.new(@version, @encoding, @standalone) - @rss.do_validate = @do_validate - @last_element = @rss - @proc_stack.push Proc.new { |text, tags| - @rss.validate_for_stream(tags) if @do_validate - } + CONTENT_PATTERN = /\s*([^=]+)=(["'])([^\2]+?)\2/ + def parse_pi_content(content) + params = {} + content.scan(CONTENT_PATTERN) do |name, quote, value| + params[name] = value + end + params end def start_else_element(local, prefix, attrs, ns) diff --git a/lib/rss/rss.rb b/lib/rss/rss.rb index 6386ca3973..7ac1f42628 100644 --- a/lib/rss/rss.rb +++ b/lib/rss/rss.rb @@ -1,7 +1,9 @@ require "time" +require "English" require "rss/utils" require "rss/converter" +require "rss/xml-stylesheet" module RSS @@ -365,7 +367,6 @@ EOC def initialize(do_validate=true) @converter = nil - @output_encoding = nil @do_validate = do_validate initialize_variables end @@ -561,4 +562,44 @@ EOC end + module RootElementMixin + + attr_reader :output_encoding + + def initialize(rss_version, version=nil, encoding=nil, standalone=nil) + super() + @rss_version = rss_version + @version = version || '1.0' + @encoding = encoding + @standalone = standalone + @output_encoding = nil + end + + def output_encoding=(enc) + @output_encoding = enc + self.converter = Converter.new(@output_encoding, @encoding) + end + + private + def xmldecl + rv = %Q[<?xml version="#{@version}"] + if @output_encoding or @encoding + rv << %Q[ encoding="#{@output_encoding or @encoding}"] + end + rv << %Q[ standalone="#{@standalone}"] if @standalone + rv << '?>' + rv + end + + def ns_declaration + rv = '' + self.class::NSPOOL.each do |prefix, uri| + prefix = ":#{prefix}" unless prefix.empty? + rv << %Q|\n\txmlns#{prefix}="#{html_escape(uri)}"| + end + rv + end + + end + end diff --git a/lib/rss/utils.rb b/lib/rss/utils.rb index cd0f9da739..ae6f69bcf1 100644 --- a/lib/rss/utils.rb +++ b/lib/rss/utils.rb @@ -3,10 +3,8 @@ module RSS module Utils def get_file_and_line_from_caller(i=0) - tmp = caller[i].split(':') - line = tmp.pop.to_i - file = tmp.join(':') - [file, line] + file, line, = caller[i].split(':') + [file, line.to_i] end def html_escape(s) 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/xmlparser.rb b/lib/rss/xmlparser.rb index 5a62ce00a1..355a428d2d 100644 --- a/lib/rss/xmlparser.rb +++ b/lib/rss/xmlparser.rb @@ -1,4 +1,10 @@ begin + require "xml/parser" +rescue LoadError + require "xmlparser" +end + +begin require "xml/encoding-ja" rescue LoadError require "xmlencoding-ja" @@ -15,7 +21,7 @@ module RSS class REXMLLikeXMLParser < ::XML::Parser - include XML::Encoding_ja + include ::XML::Encoding_ja def listener=(listener) @listener = listener @@ -37,6 +43,10 @@ module RSS @listener.xmldecl(version, encoding, standalone == 1) end + def processingInstruction(target, content) + @listener.instruction(target, content) + end + end class XMLParserParser < BaseParser @@ -51,7 +61,7 @@ module RSS parser = REXMLLikeXMLParser.new parser.listener = @listener parser.parse(@rss) - rescue XMLParserError => e + rescue ::XML::Parser::Error => e raise NotWellFormedError.new(parser.line){e.message} end end diff --git a/lib/rss/xmlscanner.rb b/lib/rss/xmlscanner.rb index 2a80b00e69..4ab997062d 100644 --- a/lib/rss/xmlscanner.rb +++ b/lib/rss/xmlscanner.rb @@ -48,6 +48,7 @@ module RSS xmldecl(@version, @encoding, @standalone) end + alias_method(:on_pi, :instruction) alias_method(:on_chardata, :text) alias_method(:on_cdata, :text) diff --git a/test/rss/common.rb b/test/rss/common.rb index 64bf0100fc..15af960bc1 100644 --- a/test/rss/common.rb +++ b/test/rss/common.rb @@ -100,6 +100,15 @@ EOI EOT end + def make_sample_RDF + make_RDF(<<-EOR) +#{make_channel} +#{make_image} +#{make_item} +#{make_textinput} +EOR + end + def make_Rss2(content=nil, xmlns=[]) <<-EORSS #{make_xmldecl} diff --git a/test/rss/my-assertions.rb b/test/rss/my-assertions.rb index 02cedfb736..3829fdbd5c 100644 --- a/test/rss/my-assertions.rb +++ b/test/rss/my-assertions.rb @@ -84,7 +84,46 @@ module Test end end end + + def assert_xml_stylesheet_attrs(xsl, attrs) + _wrap_assertion do + normalized_attrs = {} + attrs.each do |name, value| + normalized_attrs[name.to_s] = value + end + ::RSS::XMLStyleSheet::ATTRIBUTES.each do |name| + assert_equal(normalized_attrs[name], xsl.send(name)) + end + end + end + + def assert_xml_stylesheet(target, xsl, attrs) + _wrap_assertion do + if attrs.has_key?(:href) + if !attrs.has_key?(:type) and attrs.has_key?(:guess_type) + attrs[:type] = attrs[:guess_type] + end + assert_equal("xml-stylesheet", target) + assert_xml_stylesheet_attrs(xsl, attrs) + else + assert_nil(target) + assert_equal("", xsl.to_s) + end + end + end + def assert_xml_stylesheet_pis(attrs_ary) + rdf = ::RSS::RDF.new() + xss_strs = [] + attrs_ary.each do |attrs| + xss = ::RSS::XMLStyleSheet.new(*attrs) + xss_strs.push(xss.to_s) + rdf.xml_stylesheets.push(xss) + end + pi_str = rdf.to_s.gsub(/<\?xml .*\n/, "").gsub(/\s*<rdf:RDF.*\z/m, "") + assert_equal(xss_strs.join("\n"), pi_str) + end + end end end diff --git a/test/rss/test_1.0.rb b/test/rss/test_1.0.rb index b48e765d08..9f8cd2610d 100644 --- a/test/rss/test_1.0.rb +++ b/test/rss/test_1.0.rb @@ -38,8 +38,28 @@ class TestCore < Test::Unit::TestCase end - def test_channel + def test_not_displayed_xml_stylesheets + rdf = RDF.new() + plain_rdf = rdf.to_s + 3.times do + rdf.xml_stylesheets.push(XMLStyleSheet.new) + assert_equal(plain_rdf, rdf.to_s) + end + end + def test_xml_stylesheets + [ + [{:href => "a.xsl", :type => "text/xsl"}], + [ + {:href => "a.xsl", :type => "text/xsl"}, + {:href => "a.css", :type => "text/css"}, + ], + ].each do |attrs_ary| + assert_xml_stylesheet_pis(attrs_ary) + end + end + + def test_channel about = "http://hoge.com" title = "fugafuga" link = "http://hoge.com" diff --git a/test/rss/test_xml-stylesheet.rb b/test/rss/test_xml-stylesheet.rb new file mode 100644 index 0000000000..eff5fd87e5 --- /dev/null +++ b/test/rss/test_xml-stylesheet.rb @@ -0,0 +1,109 @@ +# -*- tab-width: 2 -*- vim: ts=2 + +require "test/unit" +require "rexml/document" + +require "rss/1.0" +require "rss/xml-stylesheet" +require "common" + +class TestXMLStyleSheet < Test::Unit::TestCase + include TestRSSMixin + + def test_accessor + [ + {:href => "a.xsl", :type => "text/xsl"}, + {:media => "print", :title => "FOO"}, + {:charset => "UTF-8", :alternate => "yes"}, + ].each do |attrs| + assert_xml_stylesheet_attrs(XMLStyleSheet.new(*attrs), attrs) + end + end + + def test_to_s + [ + {:href => "a.xsl", :type => "text/xsl"}, + {:type => "text/xsl"}, + {:href => "a.xsl", :guess_type => "text/xsl"}, + {:href => "a.css", :type => "text/css"}, + {:href => "a.css", :type => "text/xsl", + :guess_type => "text/css"}, + {:href => "a.xsl", :type => "text/xsl", + :title => "sample", :media => "printer", + :charset => "UTF-8", :alternate => "yes"}, + {:href => "a.css", :guess_type => "text/css", + :alternate => "no"}, + {:type => "text/xsl", :title => "sample", + :media => "printer", :charset => "UTF-8", + :alternate => "yes"}, + ].each do |attrs| + target, contents = parse_pi(XMLStyleSheet.new(*attrs).to_s) + assert_xml_stylesheet(target, XMLStyleSheet.new(*contents), attrs) + end + end + + def test_bad_alternate + %w(a ___ ??? BAD_ALTERNATE).each do |value| + xss = XMLStyleSheet.new + assert_raise(NotAvailableValueError) do + xss.alternate = value + end + xss.do_validate = false + assert_nothing_raised do + xss.alternate = value + end + assert_nil(xss.alternate) + end + end + + def test_parse + [ + [{:href => "a.xsl", :type => "text/xsl"},], + [{:media => "print", :title => "FOO"},], + [{:charset => "UTF-8", :alternate => "yes"},], + [{:href => "a.xsl", :type => "text/xsl"}, + {:type => "text/xsl"}, + {:href => "a.xsl", :guess_type => "text/xsl"}, + {:href => "a.css", :type => "text/css"}, + {:href => "a.css", :type => "text/xsl", + :guess_type => "text/css"}, + {:href => "a.xsl", :type => "text/xsl", + :title => "sample", :media => "printer", + :charset => "UTF-8", :alternate => "yes"}, + {:href => "a.css", :guess_type => "text/css", + :alternate => "no"}, + {:type => "text/xsl", :title => "sample", + :media => "printer", :charset => "UTF-8", + :alternate => "yes"},], + ].each do |xsss| + doc = REXML::Document.new(make_sample_RDF) + root = doc.root + xsss.each do |xss| + content = xss.collect do |key, name| + %Q[#{key}="#{name}"] + end.join(" ") + pi = REXML::Instruction.new("xml-stylesheet", content) + root.previous_sibling = pi + end + rss = Parser.parse(doc.to_s) + have_href_xsss = xsss.find_all {|xss| xss.has_key?(:href)} + assert_equal(have_href_xsss.size, rss.xml_stylesheets.size) + rss.xml_stylesheets.each_with_index do |stylesheet, i| + target, = parse_pi(stylesheet.to_s) + assert_xml_stylesheet(target, stylesheet, have_href_xsss[i]) + end + end + end + + def parse_pi(pi) + /\A\s*<\?(\S+)([^(?:\?>)]+)\?>\s*\z/ =~ pi + target = $1 + dummy = REXML::Document.new("<dummy #{$2}/>").root + contents = {} + dummy.attributes.each do |name, value| + contents[name] = value + end + [target, contents] + end + +end |