summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkou <kou@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2004-03-21 09:20:47 +0000
committerkou <kou@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2004-03-21 09:20:47 +0000
commit7ead69e5b3a2b09878037f11a7dda59ae402ccbf (patch)
treef7a422e5983f2c475c831e5d70987437eb77fa69
parent2639d6dc809a3f78c96543c839d72d05e6e90ca7 (diff)
* test/rss/test_xml-stylesheet.rb: added tests for xml-stylesheet.
* lib/rss/xml-stylesheet.rb: added xml-stylesheet parsing function. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@5989 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
-rw-r--r--ChangeLog7
-rw-r--r--lib/rss/0.9.rb35
-rw-r--r--lib/rss/1.0.rb50
-rw-r--r--lib/rss/2.0.rb1
-rw-r--r--lib/rss/converter.rb12
-rw-r--r--lib/rss/parser.rb28
-rw-r--r--lib/rss/rss.rb43
-rw-r--r--lib/rss/utils.rb6
-rw-r--r--lib/rss/xml-stylesheet.rb94
-rw-r--r--lib/rss/xmlparser.rb14
-rw-r--r--lib/rss/xmlscanner.rb1
-rw-r--r--test/rss/common.rb9
-rw-r--r--test/rss/my-assertions.rb39
-rw-r--r--test/rss/test_1.0.rb22
-rw-r--r--test/rss/test_xml-stylesheet.rb109
15 files changed, 385 insertions, 85 deletions
diff --git a/ChangeLog b/ChangeLog
index dffa9dee93d..7dcab8f6843 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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 4f03333d969..bb3cc23beba 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 cd645eedb34..6c33f1695d5 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 de48c20e3c1..c83fb2c393b 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 5037606f37d..9a62431a9ec 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 aadd9658cf7..a04f7959ecc 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 6386ca39739..7ac1f426289 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 cd0f9da7398..ae6f69bcf19 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 00000000000..7862c4f278a
--- /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 5a62ce00a14..355a428d2d1 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 2a80b00e691..4ab997062dc 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 64bf0100fc7..15af960bc15 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 02cedfb7368..3829fdbd5c6 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 b48e765d08a..9f8cd2610d5 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 00000000000..eff5fd87e54
--- /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