equivalent-xml 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development do
4
+ gem "nokogiri"
5
+ gem "bundler", "~> 1.0.0"
6
+ gem "rcov", ">= 0"
7
+ gem "rspec"
8
+ end
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