equivalent-xml 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +8 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +73 -0
- data/Rakefile +29 -0
- data/lib/equivalent-xml.rb +129 -0
- data/spec/equvalent-xml_spec.rb +101 -0
- metadata +132 -0
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Michael B. Klein
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
= equivalent-xml
|
2
|
+
|
3
|
+
== Description
|
4
|
+
|
5
|
+
=== Problem
|
6
|
+
Testing XML output is difficult:
|
7
|
+
* Comparing text output is brittle due to the vagaries of serialization.
|
8
|
+
* Attribute order doesn't matter.
|
9
|
+
* Element order matters sometimes, but not always.
|
10
|
+
* Text sometimes needs to be normalized, but CDATA doesn't.
|
11
|
+
* Nodes in the same namespace don't always use the same prefix
|
12
|
+
* Etc.
|
13
|
+
|
14
|
+
=== Solution
|
15
|
+
EquivalentXml for Nokogiri
|
16
|
+
|
17
|
+
=== Use
|
18
|
+
EquivalentXml.equivalent?(node_1, node_2, opts = { :element_order => false, :normalize_whitespace => true })
|
19
|
+
|
20
|
+
node_1 and node_2 can be any Nokogiri::XML::Node descendants. The most common
|
21
|
+
use is to compare two Nokogiri::XML::Document instances.
|
22
|
+
|
23
|
+
node_1 is equivalent to node_2 if and only if:
|
24
|
+
* node_1 and node_2 are of the same class
|
25
|
+
* node_1 and node_2 are in the same namespace
|
26
|
+
* node_1 and node_2 have the same number of child nodes
|
27
|
+
(excluding ProcessingInstructions, Comments and empty Text nodes)
|
28
|
+
* For each child of node_1, there is exactly one equal child of node_2
|
29
|
+
* If called with :element_order => true, equivalent child elements must be
|
30
|
+
in the same relative position in order to be considered equal
|
31
|
+
|
32
|
+
+Element+ nodes are equivalent if they have the same name, and their
|
33
|
+
child nodesets are equal (as defined above)
|
34
|
+
|
35
|
+
+Attribute+ nodes are equivalent if their names and values match exactly
|
36
|
+
|
37
|
+
+CDATA+ nodes are equivalent if their text strings match exactly,
|
38
|
+
including leading, trailing, and internal whitespace
|
39
|
+
|
40
|
+
Non-CDATA +CharacterData+ nodes are equivalent if their text strings
|
41
|
+
match after stripping leading and trailing whitespace and collapsing
|
42
|
+
internal whitespace to a single space
|
43
|
+
|
44
|
+
+Document+ nodes are equivalent if their root nodes are equal
|
45
|
+
|
46
|
+
+ProcessingInstruction+ and +Comment+ nodes are ignored
|
47
|
+
|
48
|
+
==== Options
|
49
|
+
:element_order => true
|
50
|
+
|
51
|
+
Require elements to be in the same relative position in order to be
|
52
|
+
considered equivalent.
|
53
|
+
|
54
|
+
:normalize_whitespace => false
|
55
|
+
|
56
|
+
Don't normalize whitespace within text nodes; require text nodes to
|
57
|
+
match exactly.
|
58
|
+
|
59
|
+
== Contributing to equivalent-xml
|
60
|
+
|
61
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
62
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
63
|
+
* Fork the project
|
64
|
+
* Start a feature/bugfix branch
|
65
|
+
* Commit and push until you are happy with your contribution
|
66
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
67
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
68
|
+
|
69
|
+
== Copyright
|
70
|
+
|
71
|
+
Copyright (c) 2011 Michael B. Klein. See LICENSE.txt for
|
72
|
+
further details.
|
73
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
require 'rake/tasklib'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
|
6
|
+
begin
|
7
|
+
Bundler.setup(:default, :development)
|
8
|
+
rescue Bundler::BundlerError => e
|
9
|
+
$stderr.puts e.message
|
10
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
11
|
+
exit e.status_code
|
12
|
+
end
|
13
|
+
require 'rake'
|
14
|
+
|
15
|
+
require 'rspec/core/rake_task'
|
16
|
+
RSpec::Core::RakeTask.new do |t|
|
17
|
+
t.pattern = FileList['./spec/**/*_spec.rb']
|
18
|
+
end
|
19
|
+
|
20
|
+
task :default => :spec
|
21
|
+
|
22
|
+
Rake::RDocTask.new do |rdoc|
|
23
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
24
|
+
|
25
|
+
rdoc.rdoc_dir = 'rdoc'
|
26
|
+
rdoc.title = "equivalent-xml #{version}"
|
27
|
+
rdoc.rdoc_files.include('README*')
|
28
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
29
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module EquivalentXml
|
2
|
+
|
3
|
+
class << self
|
4
|
+
|
5
|
+
ELEMENT_NODE = 1
|
6
|
+
ATTRIBUTE_NODE = 2
|
7
|
+
TEXT_NODE = 3
|
8
|
+
CDATA_SECTION_NODE = 4
|
9
|
+
ENTITY_REFERENCE_NODE = 5
|
10
|
+
ENTITY_NODE = 6
|
11
|
+
PROCESSING_INSTRUCTION_NODE = 7
|
12
|
+
COMMENT_NODE = 8
|
13
|
+
DOCUMENT_NODE = 9
|
14
|
+
DOCUMENT_TYPE_NODE = 10
|
15
|
+
DOCUMENT_FRAGMENT_NODE = 11
|
16
|
+
NOTATION_NODE = 12
|
17
|
+
|
18
|
+
DEFAULT_OPTS = { :element_order => false, :normalize_whitespace => true }
|
19
|
+
|
20
|
+
def equivalent?(node_1, node_2, opts = {})
|
21
|
+
opts = DEFAULT_OPTS.merge(opts)
|
22
|
+
self.compare_nodes(node_1, node_2, opts)
|
23
|
+
end
|
24
|
+
|
25
|
+
def compare_nodes(node_1, node_2, opts)
|
26
|
+
if (node_1.class != node_2.class) or self.same_namespace?(node_1,node_2) == false
|
27
|
+
false
|
28
|
+
else
|
29
|
+
case node_1.node_type
|
30
|
+
when DOCUMENT_NODE
|
31
|
+
self.compare_documents(node_1,node_2,opts)
|
32
|
+
when ELEMENT_NODE
|
33
|
+
self.compare_elements(node_1,node_2,opts)
|
34
|
+
when ATTRIBUTE_NODE
|
35
|
+
self.compare_attributes(node_1,node_2,opts)
|
36
|
+
when CDATA_SECTION_NODE
|
37
|
+
self.compare_cdata(node_1,node_2,opts)
|
38
|
+
when TEXT_NODE
|
39
|
+
self.compare_text(node_1,node_2,opts)
|
40
|
+
else
|
41
|
+
self.compare_children(node_1,node_2,opts)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def compare_documents(node_1, node_2, opts)
|
47
|
+
self.equivalent?(node_1.root,node_2.root,opts)
|
48
|
+
end
|
49
|
+
|
50
|
+
def compare_elements(node_1, node_2, opts)
|
51
|
+
(node_1.name == node_2.name) && self.compare_children(node_1,node_2,opts)
|
52
|
+
end
|
53
|
+
|
54
|
+
def compare_attributes(node_1, node_2, opts)
|
55
|
+
(node_1.name == node_2.name) && (node_1.value == node_2.value)
|
56
|
+
end
|
57
|
+
|
58
|
+
def compare_text(node_1, node_2, opts)
|
59
|
+
if opts[:normalize_whitespace]
|
60
|
+
node_1.text.strip.gsub(/\s+/,' ') == node_2.text.strip.gsub(/\s+/,' ')
|
61
|
+
else
|
62
|
+
node_1.text == node_2.text
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def compare_cdata(node_1, node_2, opts)
|
67
|
+
node_1.text == node_2.text
|
68
|
+
end
|
69
|
+
|
70
|
+
def compare_children(node_1, node_2, opts)
|
71
|
+
ignore_proc = lambda do |child|
|
72
|
+
child.is_a?(Nokogiri::XML::Comment) ||
|
73
|
+
child.is_a?(Nokogiri::XML::ProcessingInstruction) ||
|
74
|
+
(child.class == Nokogiri::XML::Text && child.text.strip.empty?)
|
75
|
+
end
|
76
|
+
|
77
|
+
nodeset_1 = node_1.children.reject { |child| ignore_proc.call(child) }
|
78
|
+
nodeset_2 = node_2.children.reject { |child| ignore_proc.call(child) }
|
79
|
+
result = self.compare_nodesets(nodeset_1,nodeset_2,opts)
|
80
|
+
|
81
|
+
if node_1.respond_to?(:attribute_nodes)
|
82
|
+
attributes_1 = node_1.attribute_nodes
|
83
|
+
attributes_2 = node_2.attribute_nodes
|
84
|
+
result = result && self.compare_nodesets(attributes_1,attributes_2,opts)
|
85
|
+
end
|
86
|
+
result
|
87
|
+
end
|
88
|
+
|
89
|
+
def compare_nodesets(nodeset_1, nodeset_2, opts)
|
90
|
+
local_set_1 = nodeset_1.dup
|
91
|
+
local_set_2 = nodeset_2.dup
|
92
|
+
|
93
|
+
if local_set_1.length != local_set_2.length
|
94
|
+
return false
|
95
|
+
end
|
96
|
+
|
97
|
+
local_set_1.each do |search_node|
|
98
|
+
found_node = local_set_2.find { |test_node| self.equivalent?(search_node,test_node,opts) }
|
99
|
+
if found_node.nil?
|
100
|
+
return false
|
101
|
+
else
|
102
|
+
if search_node.is_a?(Nokogiri::XML::Element) and opts[:element_order]
|
103
|
+
if search_node.parent.elements.index(search_node) != found_node.parent.elements.index(found_node)
|
104
|
+
return false
|
105
|
+
end
|
106
|
+
end
|
107
|
+
local_set_2.delete(found_node)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
return local_set_2.length == 0
|
111
|
+
end
|
112
|
+
|
113
|
+
def same_namespace?(node_1, node_2)
|
114
|
+
unless node_1.respond_to?(:namespace) and node_2.respond_to?(:namespace)
|
115
|
+
return true
|
116
|
+
end
|
117
|
+
|
118
|
+
if node_1.namespace.nil? and node_2.namespace.nil?
|
119
|
+
return true
|
120
|
+
end
|
121
|
+
|
122
|
+
href1 = node_1.namespace.nil? ? '' : node_1.namespace.href
|
123
|
+
href2 = node_2.namespace.nil? ? '' : node_2.namespace.href
|
124
|
+
return href1 == href2
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
$:.push(File.join(File.dirname(__FILE__),'..','lib'))
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'equivalent-xml'
|
4
|
+
|
5
|
+
describe EquivalentXml do
|
6
|
+
|
7
|
+
it "should consider a document equivalent to itself" do
|
8
|
+
doc1 = Nokogiri::XML("<doc xmlns='foo:bar'><first>foo bar baz</first><second>things</second></doc>")
|
9
|
+
EquivalentXml.equivalent?(doc1,doc1).should == true
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should ensure that attributes match" do
|
13
|
+
doc1 = Nokogiri::XML("<doc xmlns='foo:bar'><first order='1'>foo bar baz</first><second>things</second></doc>")
|
14
|
+
doc2 = Nokogiri::XML("<doc xmlns='foo:bar'><first order='2'>foo bar baz</first><second>things</second></doc>")
|
15
|
+
EquivalentXml.equivalent?(doc1,doc2).should == false
|
16
|
+
|
17
|
+
doc1 = Nokogiri::XML("<doc xmlns='foo:bar'><first order='1'>foo bar baz</first><second>things</second></doc>")
|
18
|
+
doc2 = Nokogiri::XML("<doc xmlns='foo:bar'><first order='1'>foo bar baz</first><second>things</second></doc>")
|
19
|
+
EquivalentXml.equivalent?(doc1,doc2).should == true
|
20
|
+
end
|
21
|
+
|
22
|
+
it "shouldn't care about attribute order" do
|
23
|
+
doc1 = Nokogiri::XML("<doc xmlns='foo:bar'><first order='1' value='quux'>foo bar baz</first><second>things</second></doc>")
|
24
|
+
doc2 = Nokogiri::XML("<doc xmlns='foo:bar'><first value='quux' order='1'>foo bar baz</first><second>things</second></doc>")
|
25
|
+
EquivalentXml.equivalent?(doc1,doc2).should == true
|
26
|
+
end
|
27
|
+
|
28
|
+
it "shouldn't care about element order by default" do
|
29
|
+
doc1 = Nokogiri::XML("<doc xmlns='foo:bar'><first>foo bar baz</first><second>things</second></doc>")
|
30
|
+
doc2 = Nokogiri::XML("<doc xmlns='foo:bar'><second>things</second><first>foo bar baz</first></doc>")
|
31
|
+
EquivalentXml.equivalent?(doc1,doc2).should == true
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should care about element order if :element_order => true is specified" do
|
35
|
+
doc1 = Nokogiri::XML("<doc xmlns='foo:bar'><first>foo bar baz</first><second>things</second></doc>")
|
36
|
+
doc2 = Nokogiri::XML("<doc xmlns='foo:bar'><second>things</second><first>foo bar baz</first></doc>")
|
37
|
+
EquivalentXml.equivalent?(doc1,doc2,:element_order => true).should == false
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should ensure nodesets have the same number of elements" do
|
41
|
+
doc1 = Nokogiri::XML("<doc xmlns='foo:bar'><first>foo bar baz</first><second>things</second></doc>")
|
42
|
+
doc2 = Nokogiri::XML("<doc xmlns='foo:bar'><second>things</second><first>foo bar baz</first><third/></doc>")
|
43
|
+
EquivalentXml.equivalent?(doc1,doc2).should == false
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should ensure namespaces match" do
|
47
|
+
doc1 = Nokogiri::XML("<doc xmlns='foo:bar'><first>foo bar baz</first><second>things</second></doc>")
|
48
|
+
doc2 = Nokogiri::XML("<doc xmlns='foo:baz'><first>foo bar baz</first><second>things</second></doc>")
|
49
|
+
EquivalentXml.equivalent?(doc1,doc2).should == false
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should normalize simple whitespace by default" do
|
53
|
+
doc1 = Nokogiri::XML("<doc xmlns='foo:bar'><first>foo bar baz</first><second>things</second></doc>")
|
54
|
+
doc2 = Nokogiri::XML("<doc xmlns='foo:bar'><first>foo bar baz</first><second>things</second></doc>")
|
55
|
+
EquivalentXml.equivalent?(doc1,doc2).should == true
|
56
|
+
end
|
57
|
+
|
58
|
+
it "shouldn't normalize simple whitespace if :normalize_whitespace => false is specified" do
|
59
|
+
doc1 = Nokogiri::XML("<doc xmlns='foo:bar'><first>foo bar baz</first><second>things</second></doc>")
|
60
|
+
doc2 = Nokogiri::XML("<doc xmlns='foo:bar'><first>foo bar baz</first><second>things</second></doc>")
|
61
|
+
EquivalentXml.equivalent?(doc1,doc2, :normalize_whitespace => false).should == false
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should normalize complex whitespace by default" do
|
65
|
+
doc1 = Nokogiri::XML("<doc xmlns='foo:bar'><first>foo bar baz</first><second>things</second></doc>")
|
66
|
+
doc2 = Nokogiri::XML(%{<doc xmlns='foo:bar'>
|
67
|
+
<second>things</second>
|
68
|
+
<first>
|
69
|
+
foo
|
70
|
+
bar baz
|
71
|
+
</first>
|
72
|
+
</doc>})
|
73
|
+
EquivalentXml.equivalent?(doc1,doc2).should == true
|
74
|
+
end
|
75
|
+
|
76
|
+
it "shouldn't normalize complex whitespace if :normalize_whitespace => false is specified" do
|
77
|
+
doc1 = Nokogiri::XML("<doc xmlns='foo:bar'><first>foo bar baz</first><second>things</second></doc>")
|
78
|
+
doc2 = Nokogiri::XML(%{<doc xmlns='foo:bar'>
|
79
|
+
<second>things</second>
|
80
|
+
<first>
|
81
|
+
foo
|
82
|
+
bar baz
|
83
|
+
</first>
|
84
|
+
</doc>})
|
85
|
+
EquivalentXml.equivalent?(doc1,doc2, :normalize_whitespace => false).should == false
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should ignore comment nodes" do
|
89
|
+
doc1 = Nokogiri::XML("<doc xmlns='foo:bar'><first>foo bar baz</first><second>things</second></doc>")
|
90
|
+
doc2 = Nokogiri::XML(%{<doc xmlns='foo:bar'>
|
91
|
+
<second>things</second>
|
92
|
+
<!-- Comment Node -->
|
93
|
+
<first>
|
94
|
+
foo
|
95
|
+
bar baz
|
96
|
+
</first>
|
97
|
+
</doc>})
|
98
|
+
EquivalentXml.equivalent?(doc1,doc2).should == true
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
metadata
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: equivalent-xml
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 25
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Michael B. Klein
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-02-17 00:00:00 -08:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: nokogiri
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: bundler
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 23
|
44
|
+
segments:
|
45
|
+
- 1
|
46
|
+
- 0
|
47
|
+
- 0
|
48
|
+
version: 1.0.0
|
49
|
+
type: :development
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: rcov
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
type: :development
|
64
|
+
version_requirements: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: rspec
|
67
|
+
prerelease: false
|
68
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
hash: 3
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
type: :development
|
78
|
+
version_requirements: *id004
|
79
|
+
description: |-
|
80
|
+
Compares two XML Nodes (Documents, etc.) for certain semantic equivalencies.
|
81
|
+
Currently written for Nokogiri, but with an eye toward supporting multiple XML libraries
|
82
|
+
email: mbklein@gmail.com
|
83
|
+
executables: []
|
84
|
+
|
85
|
+
extensions: []
|
86
|
+
|
87
|
+
extra_rdoc_files:
|
88
|
+
- LICENSE.txt
|
89
|
+
- README.rdoc
|
90
|
+
files:
|
91
|
+
- Gemfile
|
92
|
+
- LICENSE.txt
|
93
|
+
- README.rdoc
|
94
|
+
- Rakefile
|
95
|
+
- lib/equivalent-xml.rb
|
96
|
+
- spec/equvalent-xml_spec.rb
|
97
|
+
has_rdoc: true
|
98
|
+
homepage: http://github.com/mbklein/equivalent-xml
|
99
|
+
licenses:
|
100
|
+
- MIT
|
101
|
+
post_install_message:
|
102
|
+
rdoc_options: []
|
103
|
+
|
104
|
+
require_paths:
|
105
|
+
- lib
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
+
none: false
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
hash: 3
|
112
|
+
segments:
|
113
|
+
- 0
|
114
|
+
version: "0"
|
115
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
hash: 3
|
121
|
+
segments:
|
122
|
+
- 0
|
123
|
+
version: "0"
|
124
|
+
requirements: []
|
125
|
+
|
126
|
+
rubyforge_project:
|
127
|
+
rubygems_version: 1.3.7
|
128
|
+
signing_key:
|
129
|
+
specification_version: 3
|
130
|
+
summary: Easy equivalency tests for Ruby XML
|
131
|
+
test_files:
|
132
|
+
- spec/equvalent-xml_spec.rb
|