xpath 2.1.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/xpath.rb +0 -1
- data/lib/xpath/dsl.rb +11 -1
- data/lib/xpath/version.rb +1 -1
- data/spec/fixtures/simple.html +3 -3
- data/spec/union_spec.rb +8 -8
- data/spec/xpath_spec.rb +142 -116
- metadata +6 -8
- data/lib/xpath/html.rb +0 -175
- data/spec/html_spec.rb +0 -308
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ae41f677ac864edf9c7689365012dc5f73731f61
|
4
|
+
data.tar.gz: 63f10b70109aa842ac464b8491071a286b583f4c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7683342805b10e97fcb6593148c0beedb6ad43b7087915697b4a87b4127d88d1669e9e3d6a630a019a500c2203eff4a8b6c702e8bedb313ee41ca1ad726be446
|
7
|
+
data.tar.gz: 4d9146f6cd818e364b94b95f219d40a70571c33a01faa58af9feccb7d3f40d7f0e1b0d9093ef1aec4aae2429859001fa15e341a032a4248575804886e0fd8344
|
data/lib/xpath.rb
CHANGED
data/lib/xpath/dsl.rb
CHANGED
@@ -68,7 +68,7 @@ module XPath
|
|
68
68
|
|
69
69
|
METHODS = [
|
70
70
|
# node set
|
71
|
-
:count, :id, :local_name, :namespace_uri,
|
71
|
+
:count, :id, :local_name, :namespace_uri,
|
72
72
|
# string
|
73
73
|
:string, :concat, :starts_with, :contains, :substring_before,
|
74
74
|
:substring_after, :substring, :string_length, :normalize_space,
|
@@ -86,8 +86,13 @@ module XPath
|
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
|
+
def qname
|
90
|
+
method(:name)
|
91
|
+
end
|
92
|
+
|
89
93
|
alias_method :inverse, :not
|
90
94
|
alias_method :~, :not
|
95
|
+
alias_method :!, :not
|
91
96
|
alias_method :normalize, :normalize_space
|
92
97
|
alias_method :n, :normalize_space
|
93
98
|
|
@@ -95,6 +100,7 @@ module XPath
|
|
95
100
|
[:equals, :"=", :==],
|
96
101
|
[:or, :or, :|],
|
97
102
|
[:and, :and, :&],
|
103
|
+
[:not_equals, :!=, :!=],
|
98
104
|
[:lte, :<=, :<=],
|
99
105
|
[:lt, :<, :<],
|
100
106
|
[:gte, :>=, :>=],
|
@@ -128,6 +134,10 @@ module XPath
|
|
128
134
|
|
129
135
|
alias_method :self_axis, :self
|
130
136
|
|
137
|
+
def contains_word(word)
|
138
|
+
function(:concat, " ", current.normalize_space, " ").contains(" #{word} ")
|
139
|
+
end
|
140
|
+
|
131
141
|
def one_of(*expressions)
|
132
142
|
expressions.map do |e|
|
133
143
|
current.equals(e)
|
data/lib/xpath/version.rb
CHANGED
data/spec/fixtures/simple.html
CHANGED
@@ -17,7 +17,7 @@
|
|
17
17
|
|
18
18
|
<div id="woo" title="wooDiv" data="id">
|
19
19
|
<ul><li>A list</li></ul>
|
20
|
-
<p title="gorilla">Bax</p>
|
20
|
+
<p class="cat fish dog" title="gorilla">Bax</p>
|
21
21
|
<p>Bax</p>
|
22
22
|
<p id="wooDiv">Blah</p>
|
23
23
|
</div>
|
@@ -26,7 +26,7 @@
|
|
26
26
|
|
27
27
|
<div id="preference">
|
28
28
|
<p id="is-fuzzy">allamas</p>
|
29
|
-
<p id="is-exact">llama</p>
|
29
|
+
<p class="fish" id="is-exact">llama</p>
|
30
30
|
</div>
|
31
31
|
|
32
32
|
<p id="whitespace">
|
@@ -37,7 +37,7 @@
|
|
37
37
|
</p>
|
38
38
|
|
39
39
|
<div id="moar">
|
40
|
-
<p id="impchay">chimp</p>
|
40
|
+
<p class="catfish" id="impchay">chimp</p>
|
41
41
|
<div id="elephantay">elephant</div>
|
42
42
|
<p id="amingoflay">flamingo</p>
|
43
43
|
</div>
|
data/spec/union_spec.rb
CHANGED
@@ -9,7 +9,7 @@ describe XPath::Union do
|
|
9
9
|
@expr1 = XPath.generate { |x| x.descendant(:p) }
|
10
10
|
@expr2 = XPath.generate { |x| x.descendant(:div) }
|
11
11
|
@collection = XPath::Union.new(@expr1, @expr2)
|
12
|
-
@collection.expressions.should
|
12
|
+
@collection.expressions.should eq [@expr1, @expr2]
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
@@ -20,7 +20,7 @@ describe XPath::Union do
|
|
20
20
|
@collection = XPath::Union.new(@expr1, @expr2)
|
21
21
|
exprs = []
|
22
22
|
@collection.each { |expr| exprs << expr }
|
23
|
-
exprs.should
|
23
|
+
exprs.should eq [@expr1, @expr2]
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
@@ -29,7 +29,7 @@ describe XPath::Union do
|
|
29
29
|
@expr1 = XPath.generate { |x| x.descendant(:p) }
|
30
30
|
@expr2 = XPath.generate { |x| x.descendant(:div) }
|
31
31
|
@collection = XPath::Union.new(@expr1, @expr2)
|
32
|
-
@collection.map { |expr| expr.expression }.should
|
32
|
+
@collection.map { |expr| expr.expression }.should eq [:descendant, :descendant]
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
@@ -39,9 +39,9 @@ describe XPath::Union do
|
|
39
39
|
@expr2 = XPath.generate { |x| x.descendant(:div).where(x.attr(:id) == 'foo') }
|
40
40
|
@collection = XPath::Union.new(@expr1, @expr2)
|
41
41
|
@results = doc.xpath(@collection.to_xpath)
|
42
|
-
@results[0][:title].should
|
43
|
-
@results[1].text.should
|
44
|
-
@results[2].text.should
|
42
|
+
@results[0][:title].should eq 'fooDiv'
|
43
|
+
@results[1].text.should eq 'Blah'
|
44
|
+
@results[2].text.should eq 'Bax'
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
@@ -54,9 +54,9 @@ describe XPath::Union do
|
|
54
54
|
@xpath1 = @collection.where(XPath.attr(:id) == 'foo').to_xpath
|
55
55
|
@xpath2 = @collection.where(XPath.attr(:id) == 'fooDiv').to_xpath
|
56
56
|
@results = doc.xpath(@xpath1)
|
57
|
-
@results[0][:title].should
|
57
|
+
@results[0][:title].should eq 'fooDiv'
|
58
58
|
@results = doc.xpath(@xpath2)
|
59
|
-
@results[0][:id].should
|
59
|
+
@results[0][:id].should eq 'fooDiv'
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
data/spec/xpath_spec.rb
CHANGED
@@ -20,14 +20,14 @@ describe XPath do
|
|
20
20
|
|
21
21
|
it "should work as a mixin" do
|
22
22
|
xpath = Thingy.new.foo_div.to_xpath
|
23
|
-
doc.xpath(xpath).first[:title].should
|
23
|
+
doc.xpath(xpath).first[:title].should eq 'fooDiv'
|
24
24
|
end
|
25
25
|
|
26
26
|
describe '#descendant' do
|
27
27
|
it "should find nodes that are nested below the current node" do
|
28
28
|
@results = xpath { |x| x.descendant(:p) }
|
29
|
-
@results[0].text.should
|
30
|
-
@results[1].text.should
|
29
|
+
@results[0].text.should eq "Blah"
|
30
|
+
@results[1].text.should eq "Bax"
|
31
31
|
end
|
32
32
|
|
33
33
|
it "should not find nodes outside the context" do
|
@@ -40,22 +40,22 @@ describe XPath do
|
|
40
40
|
|
41
41
|
it "should find multiple kinds of nodes" do
|
42
42
|
@results = xpath { |x| x.descendant(:p, :ul) }
|
43
|
-
@results[0].text.should
|
44
|
-
@results[3].text.should
|
43
|
+
@results[0].text.should eq 'Blah'
|
44
|
+
@results[3].text.should eq 'A list'
|
45
45
|
end
|
46
46
|
|
47
47
|
it "should find all nodes when no arguments given" do
|
48
48
|
@results = xpath { |x| x.descendant[x.attr(:id) == 'foo'].descendant }
|
49
|
-
@results[0].text.should
|
50
|
-
@results[4].text.should
|
49
|
+
@results[0].text.should eq 'Blah'
|
50
|
+
@results[4].text.should eq 'A list'
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
54
|
describe '#child' do
|
55
55
|
it "should find nodes that are nested directly below the current node" do
|
56
56
|
@results = xpath { |x| x.descendant(:div).child(:p) }
|
57
|
-
@results[0].text.should
|
58
|
-
@results[1].text.should
|
57
|
+
@results[0].text.should eq "Blah"
|
58
|
+
@results[1].text.should eq "Bax"
|
59
59
|
end
|
60
60
|
|
61
61
|
it "should not find nodes that are nested further down below the current node" do
|
@@ -65,46 +65,46 @@ describe XPath do
|
|
65
65
|
|
66
66
|
it "should find multiple kinds of nodes" do
|
67
67
|
@results = xpath { |x| x.descendant(:div).child(:p, :ul) }
|
68
|
-
@results[0].text.should
|
69
|
-
@results[3].text.should
|
68
|
+
@results[0].text.should eq 'Blah'
|
69
|
+
@results[3].text.should eq 'A list'
|
70
70
|
end
|
71
71
|
|
72
72
|
it "should find all nodes when no arguments given" do
|
73
73
|
@results = xpath { |x| x.descendant[x.attr(:id) == 'foo'].child }
|
74
|
-
@results[0].text.should
|
75
|
-
@results[3].text.should
|
74
|
+
@results[0].text.should eq 'Blah'
|
75
|
+
@results[3].text.should eq 'A list'
|
76
76
|
end
|
77
77
|
end
|
78
78
|
|
79
79
|
describe '#axis' do
|
80
80
|
it "should find nodes given the xpath axis" do
|
81
81
|
@results = xpath { |x| x.axis(:descendant, :p) }
|
82
|
-
@results[0].text.should
|
82
|
+
@results[0].text.should eq "Blah"
|
83
83
|
end
|
84
84
|
|
85
85
|
it "should find nodes given the xpath axis without a specific tag" do
|
86
86
|
@results = xpath { |x| x.descendant(:div)[x.attr(:id) == 'foo'].axis(:descendant) }
|
87
|
-
@results[0][:id].should
|
87
|
+
@results[0][:id].should eq "fooDiv"
|
88
88
|
end
|
89
89
|
end
|
90
90
|
|
91
91
|
describe '#next_sibling' do
|
92
92
|
it "should find nodes which are immediate siblings of the current node" do
|
93
|
-
xpath { |x| x.descendant(:p)[x.attr(:id) == 'fooDiv'].next_sibling(:p) }.first.text.should
|
94
|
-
xpath { |x| x.descendant(:p)[x.attr(:id) == 'fooDiv'].next_sibling(:ul, :p) }.first.text.should
|
95
|
-
xpath { |x| x.descendant(:p)[x.attr(:title) == 'monkey'].next_sibling(:ul, :p) }.first.text.should
|
93
|
+
xpath { |x| x.descendant(:p)[x.attr(:id) == 'fooDiv'].next_sibling(:p) }.first.text.should eq 'Bax'
|
94
|
+
xpath { |x| x.descendant(:p)[x.attr(:id) == 'fooDiv'].next_sibling(:ul, :p) }.first.text.should eq 'Bax'
|
95
|
+
xpath { |x| x.descendant(:p)[x.attr(:title) == 'monkey'].next_sibling(:ul, :p) }.first.text.should eq 'A list'
|
96
96
|
xpath { |x| x.descendant(:p)[x.attr(:id) == 'fooDiv'].next_sibling(:ul, :li) }.first.should be_nil
|
97
|
-
xpath { |x| x.descendant(:p)[x.attr(:id) == 'fooDiv'].next_sibling }.first.text.should
|
97
|
+
xpath { |x| x.descendant(:p)[x.attr(:id) == 'fooDiv'].next_sibling }.first.text.should eq 'Bax'
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
101
101
|
describe '#previous_sibling' do
|
102
102
|
it "should find nodes which are exactly preceding the current node" do
|
103
|
-
xpath { |x| x.descendant(:p)[x.attr(:id) == 'wooDiv'].previous_sibling(:p) }.first.text.should
|
104
|
-
xpath { |x| x.descendant(:p)[x.attr(:id) == 'wooDiv'].previous_sibling(:ul, :p) }.first.text.should
|
105
|
-
xpath { |x| x.descendant(:p)[x.attr(:title) == 'gorilla'].previous_sibling(:ul, :p) }.first.text.should
|
103
|
+
xpath { |x| x.descendant(:p)[x.attr(:id) == 'wooDiv'].previous_sibling(:p) }.first.text.should eq 'Bax'
|
104
|
+
xpath { |x| x.descendant(:p)[x.attr(:id) == 'wooDiv'].previous_sibling(:ul, :p) }.first.text.should eq 'Bax'
|
105
|
+
xpath { |x| x.descendant(:p)[x.attr(:title) == 'gorilla'].previous_sibling(:ul, :p) }.first.text.should eq 'A list'
|
106
106
|
xpath { |x| x.descendant(:p)[x.attr(:id) == 'wooDiv'].previous_sibling(:ul, :li) }.first.should be_nil
|
107
|
-
xpath { |x| x.descendant(:p)[x.attr(:id) == 'wooDiv'].previous_sibling }.first.text.should
|
107
|
+
xpath { |x| x.descendant(:p)[x.attr(:id) == 'wooDiv'].previous_sibling }.first.text.should eq 'Bax'
|
108
108
|
end
|
109
109
|
end
|
110
110
|
|
@@ -114,7 +114,7 @@ describe XPath do
|
|
114
114
|
foo_div = x.anywhere(:div).where(x.attr(:id) == 'foo')
|
115
115
|
x.descendant(:p).where(x.attr(:id) == foo_div.attr(:title))
|
116
116
|
end
|
117
|
-
@results[0].text.should
|
117
|
+
@results[0].text.should eq "Blah"
|
118
118
|
end
|
119
119
|
|
120
120
|
it "should find multiple kinds of nodes regardless of the context" do
|
@@ -123,10 +123,10 @@ describe XPath do
|
|
123
123
|
context.anywhere(:p, :ul)
|
124
124
|
end
|
125
125
|
|
126
|
-
@results[0].text.should
|
127
|
-
@results[3].text.should
|
128
|
-
@results[4].text.should
|
129
|
-
@results[6].text.should
|
126
|
+
@results[0].text.should eq 'Blah'
|
127
|
+
@results[3].text.should eq 'A list'
|
128
|
+
@results[4].text.should eq 'A list'
|
129
|
+
@results[6].text.should eq 'Bax'
|
130
130
|
end
|
131
131
|
|
132
132
|
it "should find all nodes when no arguments given regardless of the context" do
|
@@ -134,13 +134,13 @@ describe XPath do
|
|
134
134
|
context=x.descendant(:div).where(x.attr(:id)=='woo')
|
135
135
|
context.anywhere
|
136
136
|
end
|
137
|
-
@results[0].name.should
|
138
|
-
@results[1].name.should
|
139
|
-
@results[2].name.should
|
140
|
-
@results[6].text.should
|
141
|
-
@results[10].text.should
|
142
|
-
@results[13].text.should
|
143
|
-
@results[15].text.should
|
137
|
+
@results[0].name.should eq 'html'
|
138
|
+
@results[1].name.should eq 'head'
|
139
|
+
@results[2].name.should eq 'body'
|
140
|
+
@results[6].text.should eq 'Blah'
|
141
|
+
@results[10].text.should eq 'A list'
|
142
|
+
@results[13].text.should eq 'A list'
|
143
|
+
@results[15].text.should eq 'Bax'
|
144
144
|
end
|
145
145
|
|
146
146
|
end
|
@@ -150,7 +150,7 @@ describe XPath do
|
|
150
150
|
@results = xpath do |x|
|
151
151
|
x.descendant(:div).where(x.attr(:title).contains('ooD'))
|
152
152
|
end
|
153
|
-
@results[0][:id].should
|
153
|
+
@results[0][:id].should eq "foo"
|
154
154
|
end
|
155
155
|
|
156
156
|
it "should find nodes that contain the given expression" do
|
@@ -158,7 +158,18 @@ describe XPath do
|
|
158
158
|
expression = x.anywhere(:div).where(x.attr(:title) == 'fooDiv').attr(:id)
|
159
159
|
x.descendant(:div).where(x.attr(:title).contains(expression))
|
160
160
|
end
|
161
|
-
@results[0][:id].should
|
161
|
+
@results[0][:id].should eq "foo"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
describe "#contains_word" do
|
166
|
+
it "should find nodes that contain the given word in its entirety" do
|
167
|
+
@results = xpath do |x|
|
168
|
+
x.descendant.where(x.attr(:class).contains_word('fish'))
|
169
|
+
end
|
170
|
+
@results[0].text.should eq "Bax"
|
171
|
+
@results[1].text.should eq "llama"
|
172
|
+
@results.length.should eq 2
|
162
173
|
end
|
163
174
|
end
|
164
175
|
|
@@ -167,9 +178,9 @@ describe XPath do
|
|
167
178
|
@results = xpath do |x|
|
168
179
|
x.descendant(:*).where(x.attr(:id).starts_with('foo'))
|
169
180
|
end
|
170
|
-
@results.size.should
|
171
|
-
@results[0][:id].should
|
172
|
-
@results[1][:id].should
|
181
|
+
@results.size.should eq 2
|
182
|
+
@results[0][:id].should eq "foo"
|
183
|
+
@results[1][:id].should eq "fooDiv"
|
173
184
|
end
|
174
185
|
|
175
186
|
it "should find nodes that contain the given expression" do
|
@@ -177,17 +188,17 @@ describe XPath do
|
|
177
188
|
expression = x.anywhere(:div).where(x.attr(:title) == 'fooDiv').attr(:id)
|
178
189
|
x.descendant(:div).where(x.attr(:title).starts_with(expression))
|
179
190
|
end
|
180
|
-
@results[0][:id].should
|
191
|
+
@results[0][:id].should eq "foo"
|
181
192
|
end
|
182
193
|
end
|
183
194
|
|
184
195
|
describe '#text' do
|
185
196
|
it "should select a node's text" do
|
186
197
|
@results = xpath { |x| x.descendant(:p).where(x.text == 'Bax') }
|
187
|
-
@results[0].text.should
|
188
|
-
@results[1][:title].should
|
198
|
+
@results[0].text.should eq 'Bax'
|
199
|
+
@results[1][:title].should eq 'monkey'
|
189
200
|
@results = xpath { |x| x.descendant(:div).where(x.descendant(:p).text == 'Bax') }
|
190
|
-
@results[0][:title].should
|
201
|
+
@results[0][:title].should eq 'fooDiv'
|
191
202
|
end
|
192
203
|
end
|
193
204
|
|
@@ -195,14 +206,14 @@ describe XPath do
|
|
195
206
|
context "when called with one argument" do
|
196
207
|
it "should select the part of a string after the specified character" do
|
197
208
|
@results = xpath { |x| x.descendant(:span).where(x.attr(:id) == "substring").text.substring(7) }
|
198
|
-
@results.should
|
209
|
+
@results.should eq "there"
|
199
210
|
end
|
200
211
|
end
|
201
212
|
|
202
213
|
context "when called with two arguments" do
|
203
214
|
it "should select the part of a string after the specified character, up to the given length" do
|
204
215
|
@results = xpath { |x| x.descendant(:span).where(x.attr(:id) == "substring").text.substring(2, 4) }
|
205
|
-
@results.should
|
216
|
+
@results.should eq "ello"
|
206
217
|
end
|
207
218
|
end
|
208
219
|
end
|
@@ -210,63 +221,78 @@ describe XPath do
|
|
210
221
|
describe '#function' do
|
211
222
|
it "should call the given xpath function" do
|
212
223
|
@results = xpath { |x| x.function(:boolean, x.function(:true) == x.function(:false)) }
|
213
|
-
@results.should
|
224
|
+
@results.should eq false
|
214
225
|
end
|
215
226
|
end
|
216
227
|
|
217
228
|
describe '#method' do
|
218
229
|
it "should call the given xpath function with the current node as the first argument" do
|
219
230
|
@results = xpath { |x| x.descendant(:span).where(x.attr(:id) == "string-length").text.method(:"string-length") }
|
220
|
-
@results.should
|
231
|
+
@results.should eq 11
|
221
232
|
end
|
222
233
|
end
|
223
234
|
|
224
235
|
describe '#string_length' do
|
225
236
|
it "should return the length of a string" do
|
226
237
|
@results = xpath { |x| x.descendant(:span).where(x.attr(:id) == "string-length").text.string_length }
|
227
|
-
@results.should
|
238
|
+
@results.should eq 11
|
228
239
|
end
|
229
240
|
end
|
230
241
|
|
231
242
|
describe '#where' do
|
232
243
|
it "should limit the expression to find only certain nodes" do
|
233
|
-
xpath { |x| x.descendant(:div).where(:"@id = 'foo'") }.first[:title].should
|
244
|
+
xpath { |x| x.descendant(:div).where(:"@id = 'foo'") }.first[:title].should eq "fooDiv"
|
234
245
|
end
|
235
246
|
|
236
247
|
it "should be aliased as []" do
|
237
|
-
xpath { |x| x.descendant(:div)[:"@id = 'foo'"] }.first[:title].should
|
248
|
+
xpath { |x| x.descendant(:div)[:"@id = 'foo'"] }.first[:title].should eq "fooDiv"
|
238
249
|
end
|
239
250
|
end
|
240
251
|
|
241
252
|
describe '#inverse' do
|
242
253
|
it "should invert the expression" do
|
243
|
-
xpath { |x| x.descendant(:p).where(x.attr(:id).equals('fooDiv').inverse) }.first.text.should
|
254
|
+
xpath { |x| x.descendant(:p).where(x.attr(:id).equals('fooDiv').inverse) }.first.text.should eq 'Bax'
|
244
255
|
end
|
245
256
|
|
246
257
|
it "should be aliased as the unary tilde" do
|
247
|
-
xpath { |x| x.descendant(:p).where(~x.attr(:id).equals('fooDiv')) }.first.text.should
|
258
|
+
xpath { |x| x.descendant(:p).where(~x.attr(:id).equals('fooDiv')) }.first.text.should eq 'Bax'
|
259
|
+
end
|
260
|
+
|
261
|
+
it "should be aliased as the unary bang" do
|
262
|
+
xpath { |x| x.descendant(:p).where(!x.attr(:id).equals('fooDiv')) }.first.text.should eq 'Bax'
|
248
263
|
end
|
249
264
|
end
|
250
265
|
|
251
266
|
describe '#equals' do
|
252
267
|
it "should limit the expression to find only certain nodes" do
|
253
|
-
xpath { |x| x.descendant(:div).where(x.attr(:id).equals('foo')) }.first[:title].should
|
268
|
+
xpath { |x| x.descendant(:div).where(x.attr(:id).equals('foo')) }.first[:title].should eq "fooDiv"
|
254
269
|
end
|
255
270
|
|
256
271
|
it "should be aliased as ==" do
|
257
|
-
xpath { |x| x.descendant(:div).where(x.attr(:id) == 'foo') }.first[:title].should
|
272
|
+
xpath { |x| x.descendant(:div).where(x.attr(:id) == 'foo') }.first[:title].should eq "fooDiv"
|
258
273
|
end
|
259
274
|
end
|
260
275
|
|
276
|
+
describe '#not_equals' do
|
277
|
+
it "should match only when not equal" do
|
278
|
+
xpath { |x| x.descendant(:div).where(x.attr(:id).not_equals('bar')) }.first[:title].should eq "fooDiv"
|
279
|
+
end
|
280
|
+
|
281
|
+
it "should be aliased as !=" do
|
282
|
+
xpath { |x| x.descendant(:div).where(x.attr(:id) != 'bar') }.first[:title].should eq "fooDiv"
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
|
261
287
|
describe '#is' do
|
262
288
|
it "uses equality when :exact given" do
|
263
|
-
xpath(:exact) { |x| x.descendant(:div).where(x.attr(:id).is('foo')) }.first[:title].should
|
289
|
+
xpath(:exact) { |x| x.descendant(:div).where(x.attr(:id).is('foo')) }.first[:title].should eq "fooDiv"
|
264
290
|
xpath(:exact) { |x| x.descendant(:div).where(x.attr(:id).is('oo')) }.first.should be_nil
|
265
291
|
end
|
266
292
|
|
267
293
|
it "uses substring matching otherwise" do
|
268
|
-
xpath { |x| x.descendant(:div).where(x.attr(:id).is('foo')) }.first[:title].should
|
269
|
-
xpath { |x| x.descendant(:div).where(x.attr(:id).is('oo')) }.first[:title].should
|
294
|
+
xpath { |x| x.descendant(:div).where(x.attr(:id).is('foo')) }.first[:title].should eq "fooDiv"
|
295
|
+
xpath { |x| x.descendant(:div).where(x.attr(:id).is('oo')) }.first[:title].should eq "fooDiv"
|
270
296
|
end
|
271
297
|
end
|
272
298
|
|
@@ -276,9 +302,9 @@ describe XPath do
|
|
276
302
|
p = x.anywhere(:div).where(x.attr(:id) == 'foo').attr(:title)
|
277
303
|
x.descendant(:*).where(x.attr(:id).one_of('foo', p, 'baz'))
|
278
304
|
end
|
279
|
-
@results[0][:title].should
|
280
|
-
@results[1].text.should
|
281
|
-
@results[2][:title].should
|
305
|
+
@results[0][:title].should eq "fooDiv"
|
306
|
+
@results[1].text.should eq "Blah"
|
307
|
+
@results[2][:title].should eq "bazDiv"
|
282
308
|
end
|
283
309
|
end
|
284
310
|
|
@@ -287,14 +313,14 @@ describe XPath do
|
|
287
313
|
@results = xpath do |x|
|
288
314
|
x.descendant(:*).where(x.contains('Bax').and(x.attr(:title).equals('monkey')))
|
289
315
|
end
|
290
|
-
@results[0][:title].should
|
316
|
+
@results[0][:title].should eq "monkey"
|
291
317
|
end
|
292
318
|
|
293
319
|
it "should be aliased as ampersand (&)" do
|
294
320
|
@results = xpath do |x|
|
295
321
|
x.descendant(:*).where(x.contains('Bax') & x.attr(:title).equals('monkey'))
|
296
322
|
end
|
297
|
-
@results[0][:title].should
|
323
|
+
@results[0][:title].should eq "monkey"
|
298
324
|
end
|
299
325
|
end
|
300
326
|
|
@@ -303,57 +329,57 @@ describe XPath do
|
|
303
329
|
@results = xpath do |x|
|
304
330
|
x.descendant(:*).where(x.attr(:id).equals('foo').or(x.attr(:id).equals('fooDiv')))
|
305
331
|
end
|
306
|
-
@results[0][:title].should
|
307
|
-
@results[1].text.should
|
332
|
+
@results[0][:title].should eq "fooDiv"
|
333
|
+
@results[1].text.should eq "Blah"
|
308
334
|
end
|
309
335
|
|
310
336
|
it "should be aliased as pipe (|)" do
|
311
337
|
@results = xpath do |x|
|
312
338
|
x.descendant(:*).where(x.attr(:id).equals('foo') | x.attr(:id).equals('fooDiv'))
|
313
339
|
end
|
314
|
-
@results[0][:title].should
|
315
|
-
@results[1].text.should
|
340
|
+
@results[0][:title].should eq "fooDiv"
|
341
|
+
@results[1].text.should eq "Blah"
|
316
342
|
end
|
317
343
|
end
|
318
344
|
|
319
345
|
describe '#attr' do
|
320
346
|
it "should be an attribute" do
|
321
347
|
@results = xpath { |x| x.descendant(:div).where(x.attr(:id)) }
|
322
|
-
@results[0][:title].should
|
323
|
-
@results[1][:title].should
|
348
|
+
@results[0][:title].should eq "barDiv"
|
349
|
+
@results[1][:title].should eq "fooDiv"
|
324
350
|
end
|
325
351
|
end
|
326
352
|
|
327
353
|
describe '#css' do
|
328
354
|
it "should find nodes by the given CSS selector" do
|
329
355
|
@results = xpath { |x| x.css('#preference p') }
|
330
|
-
@results[0].text.should
|
331
|
-
@results[1].text.should
|
356
|
+
@results[0].text.should eq 'allamas'
|
357
|
+
@results[1].text.should eq 'llama'
|
332
358
|
end
|
333
359
|
|
334
360
|
it "should respect previous expression" do
|
335
361
|
@results = xpath { |x| x.descendant[x.attr(:id) == 'moar'].css('p') }
|
336
|
-
@results[0].text.should
|
337
|
-
@results[1].text.should
|
362
|
+
@results[0].text.should eq 'chimp'
|
363
|
+
@results[1].text.should eq 'flamingo'
|
338
364
|
end
|
339
365
|
|
340
366
|
it "should be composable" do
|
341
367
|
@results = xpath { |x| x.css('#moar').descendant(:p) }
|
342
|
-
@results[0].text.should
|
343
|
-
@results[1].text.should
|
368
|
+
@results[0].text.should eq 'chimp'
|
369
|
+
@results[1].text.should eq 'flamingo'
|
344
370
|
end
|
345
371
|
|
346
372
|
it "should allow comma separated selectors" do
|
347
373
|
@results = xpath { |x| x.descendant[x.attr(:id) == 'moar'].css('div, p') }
|
348
|
-
@results[0].text.should
|
349
|
-
@results[1].text.should
|
350
|
-
@results[2].text.should
|
374
|
+
@results[0].text.should eq 'chimp'
|
375
|
+
@results[1].text.should eq 'elephant'
|
376
|
+
@results[2].text.should eq 'flamingo'
|
351
377
|
end
|
352
378
|
end
|
353
379
|
|
354
|
-
describe '#
|
380
|
+
describe '#qname' do
|
355
381
|
it "should match the node's name" do
|
356
|
-
xpath { |x| x.descendant(:*).where(x.
|
382
|
+
xpath { |x| x.descendant(:*).where(x.qname == 'ul') }.first.text.should eq "A list"
|
357
383
|
end
|
358
384
|
end
|
359
385
|
|
@@ -365,9 +391,9 @@ describe XPath do
|
|
365
391
|
@xpath1 = @collection.where(XPath.attr(:id) == 'foo').to_xpath
|
366
392
|
@xpath2 = @collection.where(XPath.attr(:id) == 'fooDiv').to_xpath
|
367
393
|
@results = doc.xpath(@xpath1)
|
368
|
-
@results[0][:title].should
|
394
|
+
@results[0][:title].should eq 'fooDiv'
|
369
395
|
@results = doc.xpath(@xpath2)
|
370
|
-
@results[0][:id].should
|
396
|
+
@results[0][:id].should eq 'fooDiv'
|
371
397
|
end
|
372
398
|
|
373
399
|
it "should be aliased as +" do
|
@@ -377,119 +403,119 @@ describe XPath do
|
|
377
403
|
@xpath1 = @collection.where(XPath.attr(:id) == 'foo').to_xpath
|
378
404
|
@xpath2 = @collection.where(XPath.attr(:id) == 'fooDiv').to_xpath
|
379
405
|
@results = doc.xpath(@xpath1)
|
380
|
-
@results[0][:title].should
|
406
|
+
@results[0][:title].should eq 'fooDiv'
|
381
407
|
@results = doc.xpath(@xpath2)
|
382
|
-
@results[0][:id].should
|
408
|
+
@results[0][:id].should eq 'fooDiv'
|
383
409
|
end
|
384
410
|
end
|
385
411
|
|
386
412
|
describe "#last" do
|
387
413
|
it "returns the number of elements in the context" do
|
388
414
|
@results = xpath { |x| x.descendant(:p)[XPath.position() == XPath.last()] }
|
389
|
-
@results[0].text.should
|
390
|
-
@results[1].text.should
|
391
|
-
@results[2].text.should
|
415
|
+
@results[0].text.should eq "Bax"
|
416
|
+
@results[1].text.should eq "Blah"
|
417
|
+
@results[2].text.should eq "llama"
|
392
418
|
end
|
393
419
|
end
|
394
420
|
|
395
421
|
describe "#position" do
|
396
422
|
it "returns the position of elements in the context" do
|
397
423
|
@results = xpath { |x| x.descendant(:p)[XPath.position() == 2] }
|
398
|
-
@results[0].text.should
|
399
|
-
@results[1].text.should
|
424
|
+
@results[0].text.should eq "Bax"
|
425
|
+
@results[1].text.should eq "Bax"
|
400
426
|
end
|
401
427
|
end
|
402
428
|
|
403
429
|
describe "#count" do
|
404
430
|
it "counts the number of occurrences" do
|
405
431
|
@results = xpath { |x| x.descendant(:div)[x.descendant(:p).count == 2] }
|
406
|
-
@results[0][:id].should
|
432
|
+
@results[0][:id].should eq "preference"
|
407
433
|
end
|
408
434
|
end
|
409
435
|
|
410
436
|
describe "#lte" do
|
411
437
|
it "checks lesser than or equal" do
|
412
438
|
@results = xpath { |x| x.descendant(:p)[XPath.position() <= 2] }
|
413
|
-
@results[0].text.should
|
414
|
-
@results[1].text.should
|
415
|
-
@results[2][:title].should
|
416
|
-
@results[3].text.should
|
439
|
+
@results[0].text.should eq "Blah"
|
440
|
+
@results[1].text.should eq "Bax"
|
441
|
+
@results[2][:title].should eq "gorilla"
|
442
|
+
@results[3].text.should eq "Bax"
|
417
443
|
end
|
418
444
|
end
|
419
445
|
|
420
446
|
describe "#lt" do
|
421
447
|
it "checks lesser than" do
|
422
448
|
@results = xpath { |x| x.descendant(:p)[XPath.position() < 2] }
|
423
|
-
@results[0].text.should
|
424
|
-
@results[1][:title].should
|
449
|
+
@results[0].text.should eq "Blah"
|
450
|
+
@results[1][:title].should eq "gorilla"
|
425
451
|
end
|
426
452
|
end
|
427
453
|
|
428
454
|
describe "#gte" do
|
429
455
|
it "checks greater than or equal" do
|
430
456
|
@results = xpath { |x| x.descendant(:p)[XPath.position() >= 2] }
|
431
|
-
@results[0].text.should
|
432
|
-
@results[1][:title].should
|
433
|
-
@results[2].text.should
|
434
|
-
@results[3].text.should
|
457
|
+
@results[0].text.should eq "Bax"
|
458
|
+
@results[1][:title].should eq "monkey"
|
459
|
+
@results[2].text.should eq "Bax"
|
460
|
+
@results[3].text.should eq "Blah"
|
435
461
|
end
|
436
462
|
end
|
437
463
|
|
438
464
|
describe "#gt" do
|
439
465
|
it "checks greater than" do
|
440
466
|
@results = xpath { |x| x.descendant(:p)[XPath.position() > 2] }
|
441
|
-
@results[0][:title].should
|
442
|
-
@results[1].text.should
|
467
|
+
@results[0][:title].should eq "monkey"
|
468
|
+
@results[1].text.should eq "Blah"
|
443
469
|
end
|
444
470
|
end
|
445
471
|
|
446
472
|
describe "#plus" do
|
447
473
|
it "adds stuff" do
|
448
474
|
@results = xpath { |x| x.descendant(:p)[XPath.position().plus(1) == 2] }
|
449
|
-
@results[0][:id].should
|
450
|
-
@results[1][:title].should
|
475
|
+
@results[0][:id].should eq "fooDiv"
|
476
|
+
@results[1][:title].should eq "gorilla"
|
451
477
|
end
|
452
478
|
end
|
453
479
|
|
454
480
|
describe "#minus" do
|
455
481
|
it "subtracts stuff" do
|
456
482
|
@results = xpath { |x| x.descendant(:p)[XPath.position().minus(1) == 0] }
|
457
|
-
@results[0][:id].should
|
458
|
-
@results[1][:title].should
|
483
|
+
@results[0][:id].should eq "fooDiv"
|
484
|
+
@results[1][:title].should eq "gorilla"
|
459
485
|
end
|
460
486
|
end
|
461
487
|
|
462
488
|
describe "#multiply" do
|
463
489
|
it "multiplies stuff" do
|
464
490
|
@results = xpath { |x| x.descendant(:p)[XPath.position() * 3 == 3] }
|
465
|
-
@results[0][:id].should
|
466
|
-
@results[1][:title].should
|
491
|
+
@results[0][:id].should eq "fooDiv"
|
492
|
+
@results[1][:title].should eq "gorilla"
|
467
493
|
end
|
468
494
|
end
|
469
495
|
|
470
496
|
describe "#divide" do
|
471
497
|
it "divides stuff" do
|
472
498
|
@results = xpath { |x| x.descendant(:p)[XPath.position() / 2 == 1] }
|
473
|
-
@results[0].text.should
|
474
|
-
@results[1].text.should
|
499
|
+
@results[0].text.should eq "Bax"
|
500
|
+
@results[1].text.should eq "Bax"
|
475
501
|
end
|
476
502
|
end
|
477
503
|
|
478
504
|
describe "#mod" do
|
479
505
|
it "take modulo" do
|
480
506
|
@results = xpath { |x| x.descendant(:p)[XPath.position() % 2 == 1] }
|
481
|
-
@results[0].text.should
|
482
|
-
@results[1][:title].should
|
483
|
-
@results[2][:title].should
|
507
|
+
@results[0].text.should eq "Blah"
|
508
|
+
@results[1][:title].should eq "monkey"
|
509
|
+
@results[2][:title].should eq "gorilla"
|
484
510
|
end
|
485
511
|
end
|
486
512
|
|
487
513
|
describe "#ancestor" do
|
488
514
|
it "finds ancestor nodes" do
|
489
515
|
@results = xpath { |x| x.descendant(:p)[1].ancestor }
|
490
|
-
@results[0].node_name.should
|
491
|
-
@results[1].node_name.should
|
492
|
-
@results[2][:id].should
|
516
|
+
@results[0].node_name.should eq "html"
|
517
|
+
@results[1].node_name.should eq "body"
|
518
|
+
@results[2][:id].should eq "foo"
|
493
519
|
end
|
494
520
|
end
|
495
521
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xpath
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonas Nicklas
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain:
|
11
11
|
- gem-public_cert.pem
|
12
|
-
date: 2017-
|
12
|
+
date: 2017-12-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: nokogiri
|
@@ -17,14 +17,14 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - "~>"
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: '1.
|
20
|
+
version: '1.8'
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - "~>"
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version: '1.
|
27
|
+
version: '1.8'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: rspec
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -92,7 +92,6 @@ files:
|
|
92
92
|
- lib/xpath.rb
|
93
93
|
- lib/xpath/dsl.rb
|
94
94
|
- lib/xpath/expression.rb
|
95
|
-
- lib/xpath/html.rb
|
96
95
|
- lib/xpath/literal.rb
|
97
96
|
- lib/xpath/renderer.rb
|
98
97
|
- lib/xpath/union.rb
|
@@ -100,7 +99,6 @@ files:
|
|
100
99
|
- spec/fixtures/form.html
|
101
100
|
- spec/fixtures/simple.html
|
102
101
|
- spec/fixtures/stuff.html
|
103
|
-
- spec/html_spec.rb
|
104
102
|
- spec/spec_helper.rb
|
105
103
|
- spec/union_spec.rb
|
106
104
|
- spec/xpath_spec.rb
|
@@ -116,7 +114,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
116
114
|
requirements:
|
117
115
|
- - ">="
|
118
116
|
- !ruby/object:Gem::Version
|
119
|
-
version:
|
117
|
+
version: '2.2'
|
120
118
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
119
|
requirements:
|
122
120
|
- - ">="
|
@@ -124,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
124
122
|
version: '0'
|
125
123
|
requirements: []
|
126
124
|
rubyforge_project:
|
127
|
-
rubygems_version: 2.
|
125
|
+
rubygems_version: 2.6.13
|
128
126
|
signing_key:
|
129
127
|
specification_version: 4
|
130
128
|
summary: Generate XPath expressions from Ruby
|
data/lib/xpath/html.rb
DELETED
@@ -1,175 +0,0 @@
|
|
1
|
-
module XPath
|
2
|
-
module HTML
|
3
|
-
include XPath::DSL
|
4
|
-
extend self
|
5
|
-
|
6
|
-
# Match an `a` link element.
|
7
|
-
#
|
8
|
-
# @param [String] locator
|
9
|
-
# Text, id, title, or image alt attribute of the link
|
10
|
-
#
|
11
|
-
def link(locator)
|
12
|
-
locator = locator.to_s
|
13
|
-
link = descendant(:a)[attr(:href)]
|
14
|
-
link[attr(:id).equals(locator) | string.n.is(locator) | attr(:title).is(locator) | descendant(:img)[attr(:alt).is(locator)]]
|
15
|
-
end
|
16
|
-
|
17
|
-
# Match a `submit`, `image`, or `button` element.
|
18
|
-
#
|
19
|
-
# @param [String] locator
|
20
|
-
# Value, title, id, or image alt attribute of the button
|
21
|
-
#
|
22
|
-
def button(locator)
|
23
|
-
locator = locator.to_s
|
24
|
-
button = descendant(:input)[attr(:type).one_of('submit', 'reset', 'image', 'button')][attr(:id).equals(locator) | attr(:value).is(locator) | attr(:title).is(locator)]
|
25
|
-
button += descendant(:button)[attr(:id).equals(locator) | attr(:value).is(locator) | string.n.is(locator) | attr(:title).is(locator)]
|
26
|
-
button += descendant(:input)[attr(:type).equals('image')][attr(:alt).is(locator)]
|
27
|
-
end
|
28
|
-
|
29
|
-
|
30
|
-
# Match anything returned by either {#link} or {#button}.
|
31
|
-
#
|
32
|
-
# @param [String] locator
|
33
|
-
# Text, id, title, or image alt attribute of the link or button
|
34
|
-
#
|
35
|
-
def link_or_button(locator)
|
36
|
-
link(locator) + button(locator)
|
37
|
-
end
|
38
|
-
|
39
|
-
|
40
|
-
# Match any `fieldset` element.
|
41
|
-
#
|
42
|
-
# @param [String] locator
|
43
|
-
# Legend or id of the fieldset
|
44
|
-
#
|
45
|
-
def fieldset(locator)
|
46
|
-
locator = locator.to_s
|
47
|
-
descendant(:fieldset)[attr(:id).equals(locator) | child(:legend)[string.n.is(locator)]]
|
48
|
-
end
|
49
|
-
|
50
|
-
|
51
|
-
# Match any `input`, `textarea`, or `select` element that doesn't have a
|
52
|
-
# type of `submit`, `image`, or `hidden`.
|
53
|
-
#
|
54
|
-
# @param [String] locator
|
55
|
-
# Label, id, or name of field to match
|
56
|
-
#
|
57
|
-
def field(locator)
|
58
|
-
locator = locator.to_s
|
59
|
-
xpath = descendant(:input, :textarea, :select)[~attr(:type).one_of('submit', 'image', 'hidden')]
|
60
|
-
xpath = locate_field(xpath, locator)
|
61
|
-
xpath
|
62
|
-
end
|
63
|
-
|
64
|
-
|
65
|
-
# Match any `input` or `textarea` element that can be filled with text.
|
66
|
-
# This excludes any inputs with a type of `submit`, `image`, `radio`,
|
67
|
-
# `checkbox`, `hidden`, or `file`.
|
68
|
-
#
|
69
|
-
# @param [String] locator
|
70
|
-
# Label, id, or name of field to match
|
71
|
-
#
|
72
|
-
def fillable_field(locator)
|
73
|
-
locator = locator.to_s
|
74
|
-
xpath = descendant(:input, :textarea)[~attr(:type).one_of('submit', 'image', 'radio', 'checkbox', 'hidden', 'file')]
|
75
|
-
xpath = locate_field(xpath, locator)
|
76
|
-
xpath
|
77
|
-
end
|
78
|
-
|
79
|
-
|
80
|
-
# Match any `select` element.
|
81
|
-
#
|
82
|
-
# @param [String] locator
|
83
|
-
# Label, id, or name of the field to match
|
84
|
-
#
|
85
|
-
def select(locator)
|
86
|
-
locator = locator.to_s
|
87
|
-
locate_field(descendant(:select), locator)
|
88
|
-
end
|
89
|
-
|
90
|
-
|
91
|
-
# Match any `input` element of type `checkbox`.
|
92
|
-
#
|
93
|
-
# @param [String] locator
|
94
|
-
# Label, id, or name of the checkbox to match
|
95
|
-
#
|
96
|
-
def checkbox(locator)
|
97
|
-
locator = locator.to_s
|
98
|
-
locate_field(descendant(:input)[attr(:type).equals('checkbox')], locator)
|
99
|
-
end
|
100
|
-
|
101
|
-
|
102
|
-
# Match any `input` element of type `radio`.
|
103
|
-
#
|
104
|
-
# @param [String] locator
|
105
|
-
# Label, id, or name of the radio button to match
|
106
|
-
#
|
107
|
-
def radio_button(locator)
|
108
|
-
locator = locator.to_s
|
109
|
-
locate_field(descendant(:input)[attr(:type).equals('radio')], locator)
|
110
|
-
end
|
111
|
-
|
112
|
-
|
113
|
-
# Match any `input` element of type `file`.
|
114
|
-
#
|
115
|
-
# @param [String] locator
|
116
|
-
# Label, id, or name of the file field to match
|
117
|
-
#
|
118
|
-
def file_field(locator)
|
119
|
-
locator = locator.to_s
|
120
|
-
locate_field(descendant(:input)[attr(:type).equals('file')], locator)
|
121
|
-
end
|
122
|
-
|
123
|
-
|
124
|
-
# Match an `optgroup` element.
|
125
|
-
#
|
126
|
-
# @param [String] name
|
127
|
-
# Label for the option group
|
128
|
-
#
|
129
|
-
def optgroup(locator)
|
130
|
-
locator = locator.to_s
|
131
|
-
descendant(:optgroup)[attr(:label).is(locator)]
|
132
|
-
end
|
133
|
-
|
134
|
-
|
135
|
-
# Match an `option` element.
|
136
|
-
#
|
137
|
-
# @param [String] name
|
138
|
-
# Visible text of the option
|
139
|
-
#
|
140
|
-
def option(locator)
|
141
|
-
locator = locator.to_s
|
142
|
-
descendant(:option)[string.n.is(locator)]
|
143
|
-
end
|
144
|
-
|
145
|
-
|
146
|
-
# Match any `table` element.
|
147
|
-
#
|
148
|
-
# @param [String] locator
|
149
|
-
# Caption or id of the table to match
|
150
|
-
# @option options [Array] :rows
|
151
|
-
# Content of each cell in each row to match
|
152
|
-
#
|
153
|
-
def table(locator)
|
154
|
-
locator = locator.to_s
|
155
|
-
descendant(:table)[attr(:id).equals(locator) | descendant(:caption).is(locator)]
|
156
|
-
end
|
157
|
-
|
158
|
-
# Match any 'dd' element.
|
159
|
-
#
|
160
|
-
# @param [String] locator
|
161
|
-
# Id of the 'dd' element or text from preciding 'dt' element content
|
162
|
-
def definition_description(locator)
|
163
|
-
locator = locator.to_s
|
164
|
-
descendant(:dd)[attr(:id).equals(locator) | previous_sibling(:dt)[string.n.equals(locator)] ]
|
165
|
-
end
|
166
|
-
|
167
|
-
protected
|
168
|
-
|
169
|
-
def locate_field(xpath, locator)
|
170
|
-
locate_field = xpath[attr(:id).equals(locator) | attr(:name).equals(locator) | attr(:placeholder).equals(locator) | attr(:id).equals(anywhere(:label)[string.n.is(locator)].attr(:for))]
|
171
|
-
locate_field += descendant(:label)[string.n.is(locator)].descendant(xpath)
|
172
|
-
locate_field
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
data/spec/html_spec.rb
DELETED
@@ -1,308 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'nokogiri'
|
3
|
-
|
4
|
-
describe XPath::HTML do
|
5
|
-
let(:template) { 'form' }
|
6
|
-
let(:template_path) { File.read(File.expand_path("fixtures/#{template}.html", File.dirname(__FILE__))) }
|
7
|
-
let(:doc) { Nokogiri::HTML(template_path) }
|
8
|
-
let(:type) { |example| example.metadata[:type] }
|
9
|
-
|
10
|
-
def get(*args)
|
11
|
-
all(*args).first
|
12
|
-
end
|
13
|
-
|
14
|
-
def all(*args)
|
15
|
-
doc.xpath(XPath::HTML.send(subject, *args).to_xpath(type)).map { |node| node[:data] }
|
16
|
-
end
|
17
|
-
|
18
|
-
describe '#link' do
|
19
|
-
subject { :link }
|
20
|
-
|
21
|
-
it("finds links by id") { get('some-id').should == 'link-id' }
|
22
|
-
it("finds links by content") { get('An awesome link').should == 'link-text' }
|
23
|
-
it("finds links by content regardless of whitespace") { get('My whitespaced link').should == 'link-whitespace' }
|
24
|
-
it("finds links with child tags by content") { get('An emphatic link').should == 'link-children' }
|
25
|
-
it("finds links by the content of their child tags") { get('emphatic').should == 'link-children' }
|
26
|
-
it("finds links by approximate content") { get('awesome').should == 'link-text' }
|
27
|
-
it("finds links by title") { get('My title').should == 'link-title' }
|
28
|
-
it("finds links by approximate title") { get('title').should == 'link-title' }
|
29
|
-
it("finds links by image's alt attribute") { get('Alt link').should == 'link-img' }
|
30
|
-
it("finds links by image's approximate alt attribute") { get('Alt').should == 'link-img' }
|
31
|
-
it("does not find links without href attriutes") { get('Wrong Link').should be_nil }
|
32
|
-
it("casts to string") { get(:'some-id').should == 'link-id' }
|
33
|
-
|
34
|
-
context "with exact match", :type => :exact do
|
35
|
-
it("finds links by content") { get('An awesome link').should == 'link-text' }
|
36
|
-
it("does not find links by approximate content") { get('awesome').should be_nil }
|
37
|
-
it("finds links by title") { get('My title').should == 'link-title' }
|
38
|
-
it("does not find links by approximate title") { get('title').should be_nil }
|
39
|
-
it("finds links by image's alt attribute") { get('Alt link').should == 'link-img' }
|
40
|
-
it("does not find links by image's approximate alt attribute") { get('Alt').should be_nil }
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
describe '#button' do
|
45
|
-
subject { :button }
|
46
|
-
|
47
|
-
context "with submit type" do
|
48
|
-
it("finds buttons by id") { get('submit-with-id').should == 'id-submit' }
|
49
|
-
it("finds buttons by value") { get('submit-with-value').should == 'value-submit' }
|
50
|
-
it("finds buttons by approximate value") { get('mit-with-val').should == 'value-submit' }
|
51
|
-
it("finds buttons by title") { get('My submit title').should == 'title-submit' }
|
52
|
-
it("finds buttons by approximate title") { get('submit title').should == 'title-submit' }
|
53
|
-
|
54
|
-
context "with exact match", :type => :exact do
|
55
|
-
it("finds buttons by value") { get('submit-with-value').should == 'value-submit' }
|
56
|
-
it("does not find buttons by approximate value") { get('mit-with-val').should be_nil }
|
57
|
-
it("finds buttons by title") { get('My submit title').should == 'title-submit' }
|
58
|
-
it("does not find buttons by approximate title") { get('submit title').should be_nil }
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
context "with reset type" do
|
63
|
-
it("finds buttons by id") { get('reset-with-id').should == 'id-reset' }
|
64
|
-
it("finds buttons by value") { get('reset-with-value').should == 'value-reset' }
|
65
|
-
it("finds buttons by approximate value") { get('set-with-val').should == 'value-reset' }
|
66
|
-
it("finds buttons by title") { get('My reset title').should == 'title-reset' }
|
67
|
-
it("finds buttons by approximate title") { get('reset title').should == 'title-reset' }
|
68
|
-
|
69
|
-
context "with exact match", :type => :exact do
|
70
|
-
it("finds buttons by value") { get('reset-with-value').should == 'value-reset' }
|
71
|
-
it("does not find buttons by approximate value") { get('set-with-val').should be_nil }
|
72
|
-
it("finds buttons by title") { get('My reset title').should == 'title-reset' }
|
73
|
-
it("does not find buttons by approximate title") { get('reset title').should be_nil }
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
context "with button type" do
|
78
|
-
it("finds buttons by id") { get('button-with-id').should == 'id-button' }
|
79
|
-
it("finds buttons by value") { get('button-with-value').should == 'value-button' }
|
80
|
-
it("finds buttons by approximate value") { get('ton-with-val').should == 'value-button' }
|
81
|
-
it("finds buttons by title") { get('My button title').should == 'title-button' }
|
82
|
-
it("finds buttons by approximate title") { get('button title').should == 'title-button' }
|
83
|
-
|
84
|
-
context "with exact match", :type => :exact do
|
85
|
-
it("finds buttons by value") { get('button-with-value').should == 'value-button' }
|
86
|
-
it("does not find buttons by approximate value") { get('ton-with-val').should be_nil }
|
87
|
-
it("finds buttons by title") { get('My button title').should == 'title-button' }
|
88
|
-
it("does not find buttons by approximate title") { get('button title').should be_nil }
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
context "with image type" do
|
93
|
-
it("finds buttons by id") { get('imgbut-with-id').should == 'id-imgbut' }
|
94
|
-
it("finds buttons by value") { get('imgbut-with-value').should == 'value-imgbut' }
|
95
|
-
it("finds buttons by approximate value") { get('gbut-with-val').should == 'value-imgbut' }
|
96
|
-
it("finds buttons by alt attribute") { get('imgbut-with-alt').should == 'alt-imgbut' }
|
97
|
-
it("finds buttons by approximate alt attribute") { get('mgbut-with-al').should == 'alt-imgbut' }
|
98
|
-
it("finds buttons by title") { get('My imgbut title').should == 'title-imgbut' }
|
99
|
-
it("finds buttons by approximate title") { get('imgbut title').should == 'title-imgbut' }
|
100
|
-
|
101
|
-
context "with exact match", :type => :exact do
|
102
|
-
it("finds buttons by value") { get('imgbut-with-value').should == 'value-imgbut' }
|
103
|
-
it("does not find buttons by approximate value") { get('gbut-with-val').should be_nil }
|
104
|
-
it("finds buttons by alt attribute") { get('imgbut-with-alt').should == 'alt-imgbut' }
|
105
|
-
it("does not find buttons by approximate alt attribute") { get('mgbut-with-al').should be_nil }
|
106
|
-
it("finds buttons by title") { get('My imgbut title').should == 'title-imgbut' }
|
107
|
-
it("does not find buttons by approximate title") { get('imgbut title').should be_nil }
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
context "with button tag" do
|
112
|
-
it("finds buttons by id") { get('btag-with-id').should == 'id-btag' }
|
113
|
-
it("finds buttons by value") { get('btag-with-value').should == 'value-btag' }
|
114
|
-
it("finds buttons by approximate value") { get('tag-with-val').should == 'value-btag' }
|
115
|
-
it("finds buttons by text") { get('btag-with-text').should == 'text-btag' }
|
116
|
-
it("finds buttons by text ignoring whitespace") { get('My whitespaced button').should == 'btag-with-whitespace' }
|
117
|
-
it("finds buttons by approximate text ") { get('tag-with-tex').should == 'text-btag' }
|
118
|
-
it("finds buttons with child tags by text") { get('An emphatic button').should == 'btag-with-children' }
|
119
|
-
it("finds buttons by text of their children") { get('emphatic').should == 'btag-with-children' }
|
120
|
-
it("finds buttons by title") { get('My btag title').should == 'title-btag' }
|
121
|
-
it("finds buttons by approximate title") { get('btag title').should == 'title-btag' }
|
122
|
-
|
123
|
-
context "with exact match", :type => :exact do
|
124
|
-
it("finds buttons by value") { get('btag-with-value').should == 'value-btag' }
|
125
|
-
it("does not find buttons by approximate value") { get('tag-with-val').should be_nil }
|
126
|
-
it("finds buttons by text") { get('btag-with-text').should == 'text-btag' }
|
127
|
-
it("does not find buttons by approximate text ") { get('tag-with-tex').should be_nil }
|
128
|
-
it("finds buttons by title") { get('My btag title').should == 'title-btag' }
|
129
|
-
it("does not find buttons by approximate title") { get('btag title').should be_nil }
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
context "with unkown type" do
|
134
|
-
it("does not find the button") { get('schmoo button').should be_nil }
|
135
|
-
end
|
136
|
-
|
137
|
-
it("casts to string") { get(:'tag-with-tex').should == 'text-btag' }
|
138
|
-
end
|
139
|
-
|
140
|
-
describe '#fieldset' do
|
141
|
-
subject { :fieldset }
|
142
|
-
|
143
|
-
it("finds fieldsets by id") { get('some-fieldset-id').should == 'fieldset-id' }
|
144
|
-
it("finds fieldsets by legend") { get('Some Legend').should == 'fieldset-legend' }
|
145
|
-
it("finds fieldsets by legend child tags") { get('Span Legend').should == 'fieldset-legend-span' }
|
146
|
-
it("accepts approximate legends") { get('Legend').should == 'fieldset-legend' }
|
147
|
-
it("finds nested fieldsets by legend") { get('Inner legend').should == 'fieldset-inner' }
|
148
|
-
it("casts to string") { get(:'Inner legend').should == 'fieldset-inner' }
|
149
|
-
|
150
|
-
context "with exact match", :type => :exact do
|
151
|
-
it("finds fieldsets by legend") { get('Some Legend').should == 'fieldset-legend' }
|
152
|
-
it("does not find by approximate legends") { get('Legend').should be_nil }
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
describe '#field' do
|
157
|
-
subject { :field }
|
158
|
-
|
159
|
-
context "by id" do
|
160
|
-
it("finds inputs with no type") { get('input-with-id').should == 'input-with-id-data' }
|
161
|
-
it("finds inputs with text type") { get('input-text-with-id').should == 'input-text-with-id-data' }
|
162
|
-
it("finds inputs with password type") { get('input-password-with-id').should == 'input-password-with-id-data' }
|
163
|
-
it("finds inputs with custom type") { get('input-custom-with-id').should == 'input-custom-with-id-data' }
|
164
|
-
it("finds textareas") { get('textarea-with-id').should == 'textarea-with-id-data' }
|
165
|
-
it("finds select boxes") { get('select-with-id').should == 'select-with-id-data' }
|
166
|
-
it("does not find submit buttons") { get('input-submit-with-id').should be_nil }
|
167
|
-
it("does not find image buttons") { get('input-image-with-id').should be_nil }
|
168
|
-
it("does not find hidden fields") { get('input-hidden-with-id').should be_nil }
|
169
|
-
end
|
170
|
-
|
171
|
-
context "by name" do
|
172
|
-
it("finds inputs with no type") { get('input-with-name').should == 'input-with-name-data' }
|
173
|
-
it("finds inputs with text type") { get('input-text-with-name').should == 'input-text-with-name-data' }
|
174
|
-
it("finds inputs with password type") { get('input-password-with-name').should == 'input-password-with-name-data' }
|
175
|
-
it("finds inputs with custom type") { get('input-custom-with-name').should == 'input-custom-with-name-data' }
|
176
|
-
it("finds textareas") { get('textarea-with-name').should == 'textarea-with-name-data' }
|
177
|
-
it("finds select boxes") { get('select-with-name').should == 'select-with-name-data' }
|
178
|
-
it("does not find submit buttons") { get('input-submit-with-name').should be_nil }
|
179
|
-
it("does not find image buttons") { get('input-image-with-name').should be_nil }
|
180
|
-
it("does not find hidden fields") { get('input-hidden-with-name').should be_nil }
|
181
|
-
end
|
182
|
-
|
183
|
-
context "by placeholder" do
|
184
|
-
it("finds inputs with no type") { get('input-with-placeholder').should == 'input-with-placeholder-data' }
|
185
|
-
it("finds inputs with text type") { get('input-text-with-placeholder').should == 'input-text-with-placeholder-data' }
|
186
|
-
it("finds inputs with password type") { get('input-password-with-placeholder').should == 'input-password-with-placeholder-data' }
|
187
|
-
it("finds inputs with custom type") { get('input-custom-with-placeholder').should == 'input-custom-with-placeholder-data' }
|
188
|
-
it("finds textareas") { get('textarea-with-placeholder').should == 'textarea-with-placeholder-data' }
|
189
|
-
it("does not find hidden fields") { get('input-hidden-with-placeholder').should be_nil }
|
190
|
-
end
|
191
|
-
|
192
|
-
context "by referenced label" do
|
193
|
-
it("finds inputs with no type") { get('Input with label').should == 'input-with-label-data' }
|
194
|
-
it("finds inputs with text type") { get('Input text with label').should == 'input-text-with-label-data' }
|
195
|
-
it("finds inputs with password type") { get('Input password with label').should == 'input-password-with-label-data' }
|
196
|
-
it("finds inputs with custom type") { get('Input custom with label').should == 'input-custom-with-label-data' }
|
197
|
-
it("finds textareas") { get('Textarea with label').should == 'textarea-with-label-data' }
|
198
|
-
it("finds select boxes") { get('Select with label').should == 'select-with-label-data' }
|
199
|
-
it("does not find submit buttons") { get('Input submit with label').should be_nil }
|
200
|
-
it("does not find image buttons") { get('Input image with label').should be_nil }
|
201
|
-
it("does not find hidden fields") { get('Input hidden with label').should be_nil }
|
202
|
-
end
|
203
|
-
|
204
|
-
context "by parent label" do
|
205
|
-
it("finds inputs with no type") { get('Input with parent label').should == 'input-with-parent-label-data' }
|
206
|
-
it("finds inputs with text type") { get('Input text with parent label').should == 'input-text-with-parent-label-data' }
|
207
|
-
it("finds inputs with password type") { get('Input password with parent label').should == 'input-password-with-parent-label-data' }
|
208
|
-
it("finds inputs with custom type") { get('Input custom with parent label').should == 'input-custom-with-parent-label-data' }
|
209
|
-
it("finds textareas") { get('Textarea with parent label').should == 'textarea-with-parent-label-data' }
|
210
|
-
it("finds select boxes") { get('Select with parent label').should == 'select-with-parent-label-data' }
|
211
|
-
it("does not find submit buttons") { get('Input submit with parent label').should be_nil }
|
212
|
-
it("does not find image buttons") { get('Input image with parent label').should be_nil }
|
213
|
-
it("does not find hidden fields") { get('Input hidden with parent label').should be_nil }
|
214
|
-
end
|
215
|
-
|
216
|
-
it("casts to string") { get(:'select-with-id').should == 'select-with-id-data' }
|
217
|
-
end
|
218
|
-
|
219
|
-
describe '#fillable_field' do
|
220
|
-
subject{ :fillable_field }
|
221
|
-
context "by parent label" do
|
222
|
-
it("finds inputs with text type") { get('Label text').should == 'id-text' }
|
223
|
-
it("finds inputs where label has problem chars") { get("Label text's got an apostrophe").should == 'id-problem-text' }
|
224
|
-
end
|
225
|
-
|
226
|
-
end
|
227
|
-
|
228
|
-
describe '#select' do
|
229
|
-
subject{ :select }
|
230
|
-
it("finds selects by id") { get('select-with-id').should == 'select-with-id-data' }
|
231
|
-
it("finds selects by name") { get('select-with-name').should == 'select-with-name-data' }
|
232
|
-
it("finds selects by label") { get('Select with label').should == 'select-with-label-data' }
|
233
|
-
it("finds selects by parent label") { get('Select with parent label').should == 'select-with-parent-label-data' }
|
234
|
-
it("casts to string") { get(:'Select with parent label').should == 'select-with-parent-label-data' }
|
235
|
-
end
|
236
|
-
|
237
|
-
describe '#checkbox' do
|
238
|
-
subject{ :checkbox }
|
239
|
-
it("finds checkboxes by id") { get('input-checkbox-with-id').should == 'input-checkbox-with-id-data' }
|
240
|
-
it("finds checkboxes by name") { get('input-checkbox-with-name').should == 'input-checkbox-with-name-data' }
|
241
|
-
it("finds checkboxes by label") { get('Input checkbox with label').should == 'input-checkbox-with-label-data' }
|
242
|
-
it("finds checkboxes by parent label") { get('Input checkbox with parent label').should == 'input-checkbox-with-parent-label-data' }
|
243
|
-
it("casts to string") { get(:'Input checkbox with parent label').should == 'input-checkbox-with-parent-label-data' }
|
244
|
-
end
|
245
|
-
|
246
|
-
describe '#radio_button' do
|
247
|
-
subject{ :radio_button }
|
248
|
-
it("finds radio buttons by id") { get('input-radio-with-id').should == 'input-radio-with-id-data' }
|
249
|
-
it("finds radio buttons by name") { get('input-radio-with-name').should == 'input-radio-with-name-data' }
|
250
|
-
it("finds radio buttons by label") { get('Input radio with label').should == 'input-radio-with-label-data' }
|
251
|
-
it("finds radio buttons by parent label") { get('Input radio with parent label').should == 'input-radio-with-parent-label-data' }
|
252
|
-
it("casts to string") { get(:'Input radio with parent label').should == 'input-radio-with-parent-label-data' }
|
253
|
-
end
|
254
|
-
|
255
|
-
describe '#file_field' do
|
256
|
-
subject{ :file_field }
|
257
|
-
it("finds file fields by id") { get('input-file-with-id').should == 'input-file-with-id-data' }
|
258
|
-
it("finds file fields by name") { get('input-file-with-name').should == 'input-file-with-name-data' }
|
259
|
-
it("finds file fields by label") { get('Input file with label').should == 'input-file-with-label-data' }
|
260
|
-
it("finds file fields by parent label") { get('Input file with parent label').should == 'input-file-with-parent-label-data' }
|
261
|
-
it("casts to string") { get(:'Input file with parent label').should == 'input-file-with-parent-label-data' }
|
262
|
-
end
|
263
|
-
|
264
|
-
describe "#optgroup" do
|
265
|
-
subject { :optgroup }
|
266
|
-
it("finds optgroups by label") { get('Group A').should == 'optgroup-a' }
|
267
|
-
it("finds optgroups by approximate label") { get('oup A').should == 'optgroup-a' }
|
268
|
-
it("casts to string") { get(:'Group A').should == 'optgroup-a' }
|
269
|
-
|
270
|
-
context "with exact match", :type => :exact do
|
271
|
-
it("finds by label") { get('Group A').should == 'optgroup-a' }
|
272
|
-
it("does not find by approximate label") { get('oup A').should be_nil }
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
|
-
describe '#option' do
|
277
|
-
subject{ :option }
|
278
|
-
it("finds by text") { get('Option with text').should == 'option-with-text-data' }
|
279
|
-
it("finds by approximate text") { get('Option with').should == 'option-with-text-data' }
|
280
|
-
it("casts to string") { get(:'Option with text').should == 'option-with-text-data' }
|
281
|
-
|
282
|
-
context "with exact match", :type => :exact do
|
283
|
-
it("finds by text") { get('Option with text').should == 'option-with-text-data' }
|
284
|
-
it("does not find by approximate text") { get('Option with').should be_nil }
|
285
|
-
end
|
286
|
-
end
|
287
|
-
|
288
|
-
describe "#table" do
|
289
|
-
subject {:table}
|
290
|
-
it("finds by id") { get('table-with-id').should == 'table-with-id-data' }
|
291
|
-
it("finds by caption") { get('Table with caption').should == 'table-with-caption-data' }
|
292
|
-
it("finds by approximate caption") { get('Table with').should == 'table-with-caption-data' }
|
293
|
-
it("casts to string") { get(:'Table with caption').should == 'table-with-caption-data' }
|
294
|
-
|
295
|
-
context "with exact match", :type => :exact do
|
296
|
-
it("finds by caption") { get('Table with caption').should == 'table-with-caption-data' }
|
297
|
-
it("does not find by approximate caption") { get('Table with').should be_nil }
|
298
|
-
end
|
299
|
-
end
|
300
|
-
|
301
|
-
describe "#definition_description" do
|
302
|
-
subject {:definition_description}
|
303
|
-
let(:template) {'stuff'}
|
304
|
-
it("find definition description by id") { get('latte').should == "with-id" }
|
305
|
-
it("find definition description by term") { get("Milk").should == "with-dt" }
|
306
|
-
it("casts to string") { get(:"Milk").should == "with-dt" }
|
307
|
-
end
|
308
|
-
end
|