xmatch 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +26 -12
- data/Rakefile +0 -1
- data/lib/matcher/nokogiri_extensions.rb +10 -25
- data/lib/matcher/version.rb +1 -1
- data/lib/matcher/xml.rb +45 -7
- data/spec/matcher/xml_spec.rb +163 -98
- metadata +4 -4
data/README.markdown
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
XMatch
|
2
2
|
==============
|
3
3
|
|
4
|
-
XMatch is a Ruby library for comparing two XML documents and reporting on mismatches. An XML document will match another if:
|
4
|
+
XMatch is a Ruby library for comparing two XML documents and reporting on matches and mismatches. An XML document will match another if:
|
5
5
|
|
6
6
|
* elements have the same name
|
7
7
|
* elements have the same number of children
|
@@ -14,24 +14,38 @@ XMatch uses Nokogiri for xml parsing.
|
|
14
14
|
|
15
15
|
Matching XML
|
16
16
|
------------
|
17
|
-
Given two XML documents as strings, XMatch is run by:
|
17
|
+
Given two XML documents as strings (or Nokogiri Documents), XMatch is run by:
|
18
18
|
|
19
|
-
|
20
|
-
|
19
|
+
matcher = Matcher::Xml.new(expected)
|
20
|
+
matcher.match(actual)
|
21
21
|
|
22
22
|
A matcher provides access to the match information by xpath values:
|
23
23
|
|
24
|
-
|
25
|
-
|
24
|
+
matcher.matches
|
25
|
+
matcher.mismatches
|
26
26
|
|
27
27
|
Custom matchers
|
28
28
|
---------------
|
29
|
-
The actual values of some xml elements are hard to know in advance (timestamps and
|
30
|
-
to provide a good guess at a match in advance of the match being run. Custom matchers are predicates provided as Ruby Procs, identified by the xpath of the element they should be applied to.
|
29
|
+
The actual values of some xml elements are hard to know in advance (timestamps and id's being typical examples). XMatch allows custom matchers to be applied
|
30
|
+
to provide a good guess at a match in advance of the match being run. Custom matchers are predicates provided as Ruby Procs, identified by the xpath of the element they should be applied to. They can be applied to text and attribute values.
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
matcher = Matcher::Xml.new("<bookstore id='1'></bookstore>")
|
33
|
+
matcher.on("/bookstore/@id") { |actual| actual =~ /\d+/ }
|
34
|
+
matcher.match("<bookstore id='2'></bookstore>") # ==> true
|
35
|
+
|
36
|
+
An alternate syntax allows a pattern to be excluded from the match. This is useful if values are mostly matching but differ by a pattern that cannot be known in advance (e.g. id's):
|
37
|
+
|
38
|
+
matcher = Matcher::Xml.new("<book>This is book 123</book>")
|
39
|
+
matcher.on("/book/text()", :excluding => /\d{3}$/)
|
40
|
+
matcher.match("<book>This is book 456</book>") # ==> true
|
41
|
+
|
42
|
+
will exclude the first three digits from the actual match. A single capture group used in the pattern:
|
43
|
+
|
44
|
+
matcher = Matcher::Xml.new("<book>Book 123 is here</book>")
|
45
|
+
matcher.on("/book/text()", :excluding => /\.*s(\d{3,6})\s.*/)
|
46
|
+
matcher.match("<book>Book 123456 is here</book>") # ==> true
|
47
|
+
|
48
|
+
will exclude only the matching capture.
|
35
49
|
|
36
50
|
Formatting match results
|
37
51
|
------------------------
|
@@ -50,4 +64,4 @@ XMatch is packaged as a Gem. Install with:
|
|
50
64
|
Copyright
|
51
65
|
---------
|
52
66
|
|
53
|
-
Copyright (c)
|
67
|
+
Copyright (c) 2010 Peter Moran. See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -5,19 +5,16 @@ module Nokogiri
|
|
5
5
|
module XML
|
6
6
|
|
7
7
|
class Node
|
8
|
-
|
8
|
+
|
9
|
+
def match?(other, matcher)
|
10
|
+
matching(other, matcher) ? true : false
|
11
|
+
end
|
12
|
+
|
9
13
|
def matching(other, matcher)
|
10
14
|
other_elem = other.at_xpath(path)
|
11
15
|
matcher.record(self.path, false, Matcher::Xml::EXISTENCE, Matcher::Xml::NOT_FOUND) unless other_elem
|
12
16
|
other_elem
|
13
17
|
end
|
14
|
-
end
|
15
|
-
|
16
|
-
class Document
|
17
|
-
|
18
|
-
def match?(other, matcher)
|
19
|
-
matching(other, matcher)
|
20
|
-
end
|
21
18
|
|
22
19
|
end
|
23
20
|
|
@@ -25,8 +22,7 @@ module Nokogiri
|
|
25
22
|
|
26
23
|
def match?(other, matcher)
|
27
24
|
@matcher = matcher
|
28
|
-
other_elem = matching(other, matcher)
|
29
|
-
return false unless other_elem
|
25
|
+
return false unless other_elem = matching(other, matcher)
|
30
26
|
children_match?(other_elem) & attributes_match?(other_elem)
|
31
27
|
end
|
32
28
|
|
@@ -54,14 +50,8 @@ module Nokogiri
|
|
54
50
|
class Text
|
55
51
|
|
56
52
|
def match?(other, matcher)
|
57
|
-
|
58
|
-
|
59
|
-
return false unless other_elem
|
60
|
-
|
61
|
-
custom_matcher = matcher.custom_matchers[path]
|
62
|
-
match = custom_matcher ? custom_matcher.call(other_elem.content) : (content == other_elem.content)
|
63
|
-
@matcher.record(self.path, match, content, other_elem.content)
|
64
|
-
match
|
53
|
+
return false unless other_elem = matching(other, matcher)
|
54
|
+
matcher.evaluate(path, content, other_elem.content)
|
65
55
|
end
|
66
56
|
|
67
57
|
end
|
@@ -69,13 +59,8 @@ module Nokogiri
|
|
69
59
|
class Attr
|
70
60
|
|
71
61
|
def match?(other, matcher)
|
72
|
-
other_elem = matching(other, matcher)
|
73
|
-
|
74
|
-
|
75
|
-
custom_matcher = matcher.custom_matchers[path]
|
76
|
-
match = custom_matcher ? custom_matcher.call(other_elem.value) : (value == other_elem.value)
|
77
|
-
matcher.record(self.path, match, value, other_elem.value)
|
78
|
-
match
|
62
|
+
return false unless other_elem = matching(other, matcher)
|
63
|
+
matcher.evaluate(path, value, other_elem.value)
|
79
64
|
end
|
80
65
|
|
81
66
|
end
|
data/lib/matcher/version.rb
CHANGED
data/lib/matcher/xml.rb
CHANGED
@@ -3,8 +3,11 @@ require 'ostruct'
|
|
3
3
|
|
4
4
|
module Matcher
|
5
5
|
|
6
|
+
class MatchError < StandardError
|
7
|
+
end
|
8
|
+
|
6
9
|
class Xml
|
7
|
-
|
10
|
+
|
8
11
|
NOT_FOUND = "[Not found]"
|
9
12
|
EXISTENCE = "[Existence]"
|
10
13
|
UNMATCHED = "[Unmatched]"
|
@@ -17,10 +20,15 @@ module Matcher
|
|
17
20
|
@results = {}
|
18
21
|
end
|
19
22
|
|
20
|
-
def match_on(path, &blk)
|
21
|
-
|
23
|
+
def match_on(path, options = {}, &blk)
|
24
|
+
raise ArgumentError.new("Using block AND options is not supported for custom matching") if blk && !options.empty?
|
25
|
+
excluding = options[:excluding]
|
26
|
+
raise ArgumentError.new "'excluding' option must be a regular expression" if excluding && !excluding.kind_of?(Regexp)
|
27
|
+
@custom_matchers[path] = blk || options
|
22
28
|
end
|
23
29
|
|
30
|
+
alias_method :on, :match_on
|
31
|
+
|
24
32
|
def match(actual)
|
25
33
|
@results.clear
|
26
34
|
@rhs = parse(actual)
|
@@ -29,7 +37,7 @@ module Matcher
|
|
29
37
|
|
30
38
|
def record(path, result, expected, actual)
|
31
39
|
# support 0 as true (for regex matches)
|
32
|
-
r = !result || result.nil? ? false : true
|
40
|
+
r = (!result || result.nil?) ? false : true
|
33
41
|
was_custom_matched = @custom_matchers[path] ? true : false
|
34
42
|
@results[path] = OpenStruct.new(:result => r, :expected => expected, :actual => actual, :was_custom_matched => was_custom_matched)
|
35
43
|
end
|
@@ -39,17 +47,47 @@ module Matcher
|
|
39
47
|
return "mismatched" if mismatches[path]
|
40
48
|
"unmatched"
|
41
49
|
end
|
42
|
-
|
50
|
+
|
43
51
|
def matches
|
44
52
|
results_that_are(true)
|
45
53
|
end
|
46
|
-
|
54
|
+
|
47
55
|
def mismatches
|
48
56
|
results_that_are(false)
|
49
57
|
end
|
50
58
|
|
59
|
+
def evaluate(path, expected, actual)
|
60
|
+
custom_matcher = custom_matchers[path]
|
61
|
+
match = custom_matcher ? evaluate_custom_matcher(custom_matcher, expected, actual) : expected == actual
|
62
|
+
record(path, match, expected, actual)
|
63
|
+
match
|
64
|
+
end
|
65
|
+
|
51
66
|
private
|
52
|
-
|
67
|
+
|
68
|
+
def evaluate_custom_matcher(custom_matcher, expected, actual)
|
69
|
+
custom_matcher.kind_of?(Hash) ? evaluate_as_regex(custom_matcher, expected, actual) : custom_matcher.call(actual)
|
70
|
+
end
|
71
|
+
|
72
|
+
def evaluate_as_regex(custom_matcher, expected, actual)
|
73
|
+
exclude_pattern = custom_matcher[:excluding]
|
74
|
+
match_data = expected.match(exclude_pattern)
|
75
|
+
return false unless match_data
|
76
|
+
|
77
|
+
if match_data.captures.empty?
|
78
|
+
lhs = expected.sub(exclude_pattern, "")
|
79
|
+
rhs = actual.sub(exclude_pattern, "")
|
80
|
+
else
|
81
|
+
lhs_exclude = expected.match(exclude_pattern).captures.first
|
82
|
+
rhs_exclude = actual.match(exclude_pattern).captures.first
|
83
|
+
lhs = expected.sub(/#{lhs_exclude}/, "")
|
84
|
+
rhs = actual.sub(/#{rhs_exclude}/, "")
|
85
|
+
end
|
86
|
+
|
87
|
+
raise Matcher::MatchError.new("excluding option resulted in comparing empty values") if lhs.empty?
|
88
|
+
lhs == rhs
|
89
|
+
end
|
90
|
+
|
53
91
|
def results_that_are(value)
|
54
92
|
match_info = {}
|
55
93
|
@results.each { |path, info| match_info[path] = info if info.result == value}
|
data/spec/matcher/xml_spec.rb
CHANGED
@@ -7,12 +7,13 @@ describe Matcher::Xml do
|
|
7
7
|
def verify_mismatch(path, expected, actual, count = 1)
|
8
8
|
match = @xml.match(@rhs)
|
9
9
|
@xml.mismatches.should have(count).mismatch
|
10
|
-
@xml.mismatches[path]
|
11
|
-
|
12
|
-
|
10
|
+
mismatch = @xml.mismatches[path]
|
11
|
+
mismatch.result.should be_false
|
12
|
+
mismatch.expected.should == expected.to_s
|
13
|
+
mismatch.actual.should == actual
|
13
14
|
end
|
14
15
|
|
15
|
-
context "
|
16
|
+
context "when being created" do
|
16
17
|
|
17
18
|
before(:each) do
|
18
19
|
@xml = Matcher::Xml.new("<foo></foo>")
|
@@ -33,25 +34,26 @@ describe Matcher::Xml do
|
|
33
34
|
|
34
35
|
end
|
35
36
|
|
36
|
-
before(:each) do
|
37
|
-
@lhs = <<-eos
|
38
|
-
<bookstore>
|
39
|
-
<book category="COOKING">
|
40
|
-
<title lang="en">Everyday Italian</title>
|
41
|
-
</book>
|
42
|
-
</bookstore>
|
43
|
-
eos
|
44
|
-
@xml = Matcher::Xml.new(@lhs)
|
45
|
-
end
|
46
37
|
|
47
38
|
context "matching" do
|
48
39
|
|
40
|
+
before(:each) do
|
41
|
+
@lhs = <<-eos
|
42
|
+
<bookstore>
|
43
|
+
<book category="COOKING">
|
44
|
+
<title lang="en">Everyday Italian</title>
|
45
|
+
</book>
|
46
|
+
</bookstore>
|
47
|
+
eos
|
48
|
+
@xml = Matcher::Xml.new(@lhs)
|
49
|
+
end
|
50
|
+
|
49
51
|
it "should be true when documents match" do
|
50
52
|
Matcher::Xml.new(@lhs).match(@lhs.clone).should be_true
|
51
53
|
end
|
52
54
|
|
53
55
|
it "should provide empty mismatches on match" do
|
54
|
-
@xml.match(@lhs.clone)
|
56
|
+
@xml.match(@lhs.clone)
|
55
57
|
@xml.mismatches.should be_empty
|
56
58
|
end
|
57
59
|
|
@@ -68,7 +70,7 @@ describe Matcher::Xml do
|
|
68
70
|
@xml.match(rhs).should be_true
|
69
71
|
end
|
70
72
|
|
71
|
-
it "should be true when a string is matched with a document" do
|
73
|
+
it "should be true when a string is matched with a parsed document" do
|
72
74
|
rhs = <<-eos
|
73
75
|
<bookstore>
|
74
76
|
<book category="COOKING">
|
@@ -82,7 +84,7 @@ describe Matcher::Xml do
|
|
82
84
|
Matcher::Xml.new(@lhs).match(Nokogiri::XML(rhs)).should be_true
|
83
85
|
end
|
84
86
|
|
85
|
-
context "elements" do
|
87
|
+
context "against elements" do
|
86
88
|
|
87
89
|
it "should not match when rhs has an extra element" do
|
88
90
|
@rhs = <<-eos
|
@@ -108,7 +110,7 @@ describe Matcher::Xml do
|
|
108
110
|
|
109
111
|
end
|
110
112
|
|
111
|
-
context "names" do
|
113
|
+
context "against element names" do
|
112
114
|
|
113
115
|
it "should not match when rhs has a different element name" do
|
114
116
|
@rhs = <<-eos
|
@@ -123,7 +125,7 @@ describe Matcher::Xml do
|
|
123
125
|
|
124
126
|
end
|
125
127
|
|
126
|
-
context "attributes" do
|
128
|
+
context "against attributes" do
|
127
129
|
|
128
130
|
it "should not match when an attribute names don't match" do
|
129
131
|
@rhs = <<-eos
|
@@ -182,9 +184,9 @@ describe Matcher::Xml do
|
|
182
184
|
|
183
185
|
end
|
184
186
|
|
185
|
-
context "contents" do
|
187
|
+
context "against text element contents" do
|
186
188
|
|
187
|
-
it "should not match when
|
189
|
+
it "should not match when contents don't match" do
|
188
190
|
@rhs = <<-eos
|
189
191
|
<bookstore>
|
190
192
|
<book category="COOKING">
|
@@ -197,18 +199,29 @@ describe Matcher::Xml do
|
|
197
199
|
|
198
200
|
end
|
199
201
|
|
202
|
+
it "should provides all results empty by default" do
|
203
|
+
Matcher::Xml.new(@lhs).results.should be_empty
|
204
|
+
end
|
205
|
+
|
200
206
|
end
|
201
207
|
|
202
|
-
context "mismatches" do
|
208
|
+
context "with mismatches" do
|
209
|
+
|
210
|
+
before(:each) do
|
211
|
+
@lhs = <<-eos
|
212
|
+
<bookstore>
|
213
|
+
<book category="COOKING">
|
214
|
+
<title lang="en">Everyday Italian</title>
|
215
|
+
</book>
|
216
|
+
</bookstore>
|
217
|
+
eos
|
218
|
+
@xml = Matcher::Xml.new(@lhs)
|
219
|
+
end
|
203
220
|
|
204
221
|
it "should be empty to start with" do
|
205
222
|
Matcher::Xml.new(@lhs).mismatches.should be_empty
|
206
223
|
end
|
207
224
|
|
208
|
-
it "provides all results" do
|
209
|
-
Matcher::Xml.new(@lhs).results.should be_empty
|
210
|
-
end
|
211
|
-
|
212
225
|
it "should be reset when rematching" do
|
213
226
|
rhs = <<-eos
|
214
227
|
<bookstore>
|
@@ -237,9 +250,6 @@ describe Matcher::Xml do
|
|
237
250
|
it "should contain parent's path when an attribute doesn't match" do
|
238
251
|
lhs = <<-eos
|
239
252
|
<bookstore>
|
240
|
-
<book category="COOKING">
|
241
|
-
<title lang="en">Everyday Italian</title>
|
242
|
-
</book>
|
243
253
|
<book category="FOO">
|
244
254
|
<title lang="en">Everyday French</title>
|
245
255
|
</book>
|
@@ -249,9 +259,6 @@ describe Matcher::Xml do
|
|
249
259
|
|
250
260
|
@rhs = <<-eos
|
251
261
|
<bookstore>
|
252
|
-
<book category="COOKING">
|
253
|
-
<title lang="en">Everyday Italian</title>
|
254
|
-
</book>
|
255
262
|
<book foo="bar">
|
256
263
|
<title lang="en">Everyday French</title>
|
257
264
|
</book>
|
@@ -259,90 +266,148 @@ describe Matcher::Xml do
|
|
259
266
|
eos
|
260
267
|
|
261
268
|
@xml = Matcher::Xml.new(lhs)
|
262
|
-
verify_mismatch("/bookstore/book
|
269
|
+
verify_mismatch("/bookstore/book/@category", Matcher::Xml::EXISTENCE, Matcher::Xml::NOT_FOUND)
|
263
270
|
end
|
271
|
+
end
|
264
272
|
|
265
|
-
|
273
|
+
context 'with matches' do
|
266
274
|
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
xml = Matcher::Xml.new(lhs)
|
277
|
-
xml.match(lhs).should be_true
|
278
|
-
match_info = xml.matches["/bookstore"]
|
279
|
-
match_info.expected.should == "1 children"
|
280
|
-
match_info.actual.should == "1 children"
|
281
|
-
end
|
275
|
+
before(:each) do
|
276
|
+
lhs = "<bookstore><book>foo</book></bookstore>"
|
277
|
+
@xml = Matcher::Xml.new(lhs)
|
278
|
+
@xml.match(lhs).should be_true
|
279
|
+
end
|
280
|
+
|
281
|
+
it "should contain each match" do
|
282
|
+
@xml.matches.should have(3).matches
|
283
|
+
end
|
282
284
|
|
285
|
+
it "should have expected and actual results" do
|
286
|
+
match_info = @xml.matches["/bookstore"]
|
287
|
+
match_info.expected.should == "1 children"
|
288
|
+
match_info.actual.should == "1 children"
|
283
289
|
end
|
284
290
|
|
285
291
|
end
|
286
292
|
|
287
|
-
context "match results" do
|
288
|
-
|
293
|
+
context "retrieving match results" do
|
294
|
+
|
295
|
+
before(:each) do
|
296
|
+
@xml = Matcher::Xml.new("<bookstore></bookstore>")
|
297
|
+
end
|
298
|
+
|
289
299
|
it "returns 'matched' for a path that matched correctly" do
|
290
|
-
xml
|
291
|
-
xml.
|
292
|
-
xml.result_for("/bookstore").should == "matched"
|
300
|
+
@xml.match("<bookstore></bookstore>")
|
301
|
+
@xml.result_for("/bookstore").should == "matched"
|
293
302
|
end
|
294
303
|
|
295
304
|
it "returns 'unmatched' for a path that was not found" do
|
296
|
-
xml
|
297
|
-
xml.
|
298
|
-
xml.result_for("/bookstore").should == "mismatched"
|
305
|
+
@xml.match("<bookstorex></bookstorex>")
|
306
|
+
@xml.result_for("/bookstore").should == "mismatched"
|
299
307
|
end
|
300
|
-
|
308
|
+
|
301
309
|
end
|
302
|
-
|
303
|
-
context "custom matchers" do
|
304
|
-
|
305
|
-
it "can be provided" do
|
306
|
-
xml = Matcher::Xml.new("<bookstore id='1'></bookstore>", {"my path" => "my predicate"})
|
307
|
-
xml.custom_matchers.should have(1).matcher
|
308
|
-
end
|
309
|
-
|
310
|
-
it "can be used on an attribute value" do
|
311
|
-
custom_matchers = { "/bookstore/@id" => lambda {|actual| actual == '2'} }
|
312
|
-
xml = Matcher::Xml.new("<bookstore id='1'></bookstore>", custom_matchers)
|
313
|
-
xml.match("<bookstore id='2'></bookstore>").should be_true
|
314
|
-
end
|
315
|
-
|
316
|
-
it "can be used on an element value" do
|
317
|
-
custom_matchers = { "/bookstore/book/text()" => lambda {|actual| actual == 'bar'} }
|
318
|
-
xml = Matcher::Xml.new("<bookstore><book>foo</book></bookstore", custom_matchers)
|
319
|
-
xml.match("<bookstore><book>bar</book></bookstore").should be_true
|
320
|
-
end
|
321
310
|
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
311
|
+
describe "with custom matchers" do
|
312
|
+
|
313
|
+
context "provided at create time" do
|
314
|
+
|
315
|
+
it "should be stored" do
|
316
|
+
Matcher::Xml.new("<bookstore</bookstore>", {"my path" => "my predicate"}).custom_matchers.should have(1).matcher
|
317
|
+
end
|
318
|
+
|
319
|
+
it "can be used on an attribute value" do
|
320
|
+
custom_matchers = { "/bookstore/@id" => lambda {|actual| actual == '2'} }
|
321
|
+
xml = Matcher::Xml.new("<bookstore id='1'></bookstore>", custom_matchers)
|
322
|
+
xml.match("<bookstore id='2'></bookstore>").should be_true
|
323
|
+
end
|
324
|
+
|
325
|
+
it "can be used on an element value" do
|
326
|
+
custom_matchers = { "/bookstore/book/text()" => lambda {|actual| actual == 'bar'} }
|
327
|
+
xml = Matcher::Xml.new("<bookstore><book>foo</book></bookstore", custom_matchers)
|
328
|
+
xml.match("<bookstore><book>bar</book></bookstore").should be_true
|
329
|
+
end
|
330
|
+
|
336
331
|
end
|
337
332
|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
333
|
+
context "using match_on" do
|
334
|
+
|
335
|
+
before(:each) do
|
336
|
+
@matcher = Matcher::Xml.new("<bookstore><book>foo text</book></bookstore")
|
337
|
+
end
|
338
|
+
|
339
|
+
it "supports match_on with a regex predicate" do
|
340
|
+
@matcher.match_on("/bookstore/book/text()") { |actual| actual =~ /bar/ }
|
341
|
+
@matcher.match("<bookstore><book>bar</book></bookstore").should be_true
|
342
|
+
end
|
343
|
+
|
344
|
+
it "supports match_on with an equality predicate" do
|
345
|
+
@matcher.match_on("/bookstore/book/text()") { |actual| actual == "foo text" }
|
346
|
+
@matcher.match("<bookstore><book>foo text</book></bookstore").should be_true
|
347
|
+
end
|
348
|
+
|
349
|
+
it "tells if a custom matcher was used" do
|
350
|
+
@matcher.match_on("/bookstore/book/text()") { |actual| actual =~ /bar/ }
|
351
|
+
@matcher.match("<bookstore><book>bar</book></bookstore")
|
352
|
+
@matcher.matches["/bookstore/book/text()"].was_custom_matched.should be_true
|
353
|
+
@matcher.matches["/bookstore/book"].was_custom_matched.should be_false
|
354
|
+
end
|
355
|
+
|
356
|
+
it "supports 'on' style" do
|
357
|
+
@matcher.on("/bookstore/book/text()") { |actual| actual =~ /bar/ }
|
358
|
+
@matcher.match("<bookstore><book>bar</book></bookstore").should be_true
|
359
|
+
end
|
360
|
+
|
361
|
+
context "with excluding option" do
|
362
|
+
|
363
|
+
[/^\w{3}/, /^.*\s/].each do |regex|
|
364
|
+
it "supports regex matching like #{regex}" do
|
365
|
+
@matcher.match("<bookstore><book>bar text</book></bookstore").should be_false
|
366
|
+
@matcher.on("/bookstore/book/text()", :excluding => regex)
|
367
|
+
@matcher.match("<bookstore><book>bar text</book></bookstore").should be_true
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
it "should support group excluding" do
|
372
|
+
matcher = Matcher::Xml.new("<book>This is book 123</book>")
|
373
|
+
matcher.on("/book/text()", :excluding => /\d{3}$/)
|
374
|
+
matcher.match("<book>This is book 456</book>").should be_true
|
375
|
+
end
|
376
|
+
|
377
|
+
it "should support group excludes with a variable length" do
|
378
|
+
matcher = Matcher::Xml.new("<book>Book 123 is here</book>")
|
379
|
+
matcher.on("/book/text()", :excluding => /.*\s(\d{3,6})\s.*/)
|
380
|
+
matcher.match("<book>Book 123456 is here</book>").should be_true
|
381
|
+
end
|
382
|
+
|
383
|
+
it "handles a non captured group" do
|
384
|
+
matcher = Matcher::Xml.new("<book>foo text</book>")
|
385
|
+
matcher.on("/book/text()", :excluding => /(blah)/)
|
386
|
+
matcher.match("<book>bar text</book>").should be_false
|
387
|
+
end
|
388
|
+
|
389
|
+
it "should raise an error if excluding would result in matching empty values" do
|
390
|
+
@matcher.on("/bookstore/book/text()", :excluding => /.*/)
|
391
|
+
lambda { @matcher.match("<bookstore><book>foo text</book></bookstore") }.should raise_error(Matcher::MatchError)
|
392
|
+
end
|
393
|
+
|
394
|
+
it "should fail a mismatching exclude" do
|
395
|
+
@matcher.on("/bookstore/book/text()", :excluding => /^\w{1}/)
|
396
|
+
@matcher.match("<bookstore><book>bar text</book></bookstore").should be_false
|
397
|
+
end
|
398
|
+
|
399
|
+
it "throws an error when a non-regex value" do
|
400
|
+
lambda { @matcher.on("/bookstore/book/text()", :excluding => "foo") }.should raise_error(ArgumentError)
|
401
|
+
end
|
402
|
+
|
403
|
+
it "should not allow both a block and options" do
|
404
|
+
lambda { @matcher.on("foo", :excluding => /bar/) {|actual| actual =~ /foo/} }.should raise_error(ArgumentError)
|
405
|
+
end
|
406
|
+
|
407
|
+
end
|
408
|
+
|
344
409
|
end
|
345
|
-
|
410
|
+
|
346
411
|
end
|
347
412
|
|
348
413
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xmatch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 21
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
9
|
+
- 1
|
10
|
+
version: 0.2.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Peter Moran
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-12-07 00:00:00 +11:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|