xpath 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|