xpath 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/xpath.rb +13 -0
- data/lib/xpath/expression.rb +15 -10
- data/lib/xpath/html.rb +4 -16
- data/lib/xpath/version.rb +1 -1
- data/spec/fixtures/form.html +62 -5
- data/spec/fixtures/simple.html +6 -0
- data/spec/html_spec.rb +195 -0
- data/spec/xpath_spec.rb +44 -6
- metadata +16 -16
data/lib/xpath.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
1
3
|
module XPath
|
2
4
|
autoload :Expression, 'xpath/expression'
|
3
5
|
autoload :Union, 'xpath/union'
|
@@ -45,6 +47,17 @@ module XPath
|
|
45
47
|
Expression::Variable.new(name)
|
46
48
|
end
|
47
49
|
|
50
|
+
def string
|
51
|
+
Expression::StringFunction.new(current)
|
52
|
+
end
|
53
|
+
|
54
|
+
def css(selector)
|
55
|
+
paths = Nokogiri::CSS.xpath_for(selector).map do |selector|
|
56
|
+
Expression::CSS.new(current, Expression::Literal.new(selector))
|
57
|
+
end
|
58
|
+
Union.new(*paths)
|
59
|
+
end
|
60
|
+
|
48
61
|
def varstring(name)
|
49
62
|
var(name).string_literal
|
50
63
|
end
|
data/lib/xpath/expression.rb
CHANGED
@@ -25,9 +25,6 @@ module XPath
|
|
25
25
|
def initialize(left, expressions)
|
26
26
|
@left = wrap_xpath(left)
|
27
27
|
@expressions = expressions.map { |e| wrap_xpath(e) }
|
28
|
-
if @expressions.empty?
|
29
|
-
raise ArgumentError, "must specify at least one expression"
|
30
|
-
end
|
31
28
|
end
|
32
29
|
end
|
33
30
|
|
@@ -45,8 +42,10 @@ module XPath
|
|
45
42
|
def to_xpath(predicate=nil)
|
46
43
|
if @expressions.length == 1
|
47
44
|
"#{@left.to_xpath(predicate)}/#{@expressions.first.to_xpath(predicate)}"
|
48
|
-
|
45
|
+
elsif @expressions.length > 1
|
49
46
|
"#{@left.to_xpath(predicate)}/*[#{@expressions.map { |e| "self::#{e.to_xpath(predicate)}" }.join(" | ")}]"
|
47
|
+
else
|
48
|
+
"#{@left.to_xpath(predicate)}/*"
|
50
49
|
end
|
51
50
|
end
|
52
51
|
end
|
@@ -55,8 +54,10 @@ module XPath
|
|
55
54
|
def to_xpath(predicate=nil)
|
56
55
|
if @expressions.length == 1
|
57
56
|
"#{@left.to_xpath(predicate)}//#{@expressions.first.to_xpath(predicate)}"
|
58
|
-
|
57
|
+
elsif @expressions.length > 1
|
59
58
|
"#{@left.to_xpath(predicate)}//*[#{@expressions.map { |e| "self::#{e.to_xpath(predicate)}" }.join(" | ")}]"
|
59
|
+
else
|
60
|
+
"#{@left.to_xpath(predicate)}//*"
|
60
61
|
end
|
61
62
|
end
|
62
63
|
end
|
@@ -65,8 +66,10 @@ module XPath
|
|
65
66
|
def to_xpath(predicate=nil)
|
66
67
|
if @expressions.length == 1
|
67
68
|
"#{@left.to_xpath(predicate)}/following-sibling::*[1]/self::#{@expressions.first.to_xpath(predicate)}"
|
68
|
-
|
69
|
+
elsif @expressions.length > 1
|
69
70
|
"#{@left.to_xpath(predicate)}/following-sibling::*[1]/self::*[#{@expressions.map { |e| "self::#{e.to_xpath(predicate)}" }.join(" | ")}]"
|
71
|
+
else
|
72
|
+
"#{@left.to_xpath(predicate)}/following-sibling::*[1]/self::*"
|
70
73
|
end
|
71
74
|
end
|
72
75
|
end
|
@@ -211,6 +214,12 @@ module XPath
|
|
211
214
|
end
|
212
215
|
end
|
213
216
|
|
217
|
+
class CSS < Binary
|
218
|
+
def to_xpath(predicate=nil)
|
219
|
+
"#{@left.to_xpath}#{@right.to_xpath}"
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
214
223
|
def current
|
215
224
|
self
|
216
225
|
end
|
@@ -237,10 +246,6 @@ module XPath
|
|
237
246
|
Expression::Is.new(current, expression)
|
238
247
|
end
|
239
248
|
|
240
|
-
def string
|
241
|
-
Expression::StringFunction.new(current)
|
242
|
-
end
|
243
|
-
|
244
249
|
def or(expression)
|
245
250
|
Expression::Or.new(current, expression)
|
246
251
|
end
|
data/lib/xpath/html.rb
CHANGED
@@ -3,13 +3,9 @@ module XPath
|
|
3
3
|
include XPath
|
4
4
|
extend self
|
5
5
|
|
6
|
-
def from_css(css)
|
7
|
-
XPath::Union.new(*Nokogiri::CSS.xpath_for(css).map { |selector| ::XPath::Expression::Literal.new(:".#{selector}") }.flatten)
|
8
|
-
end
|
9
|
-
|
10
6
|
def link(locator)
|
11
7
|
link = descendant(:a)[attr(:href)]
|
12
|
-
link[attr(:id).equals(locator) |
|
8
|
+
link[attr(:id).equals(locator) | string.n.is(locator) | attr(:title).is(locator) | descendant(:img)[attr(:alt).is(locator)]]
|
13
9
|
end
|
14
10
|
|
15
11
|
def content(locator)
|
@@ -18,7 +14,7 @@ module XPath
|
|
18
14
|
|
19
15
|
def button(locator)
|
20
16
|
button = descendant(:input)[attr(:type).one_of('submit', 'image', 'button')][attr(:id).equals(locator) | attr(:value).is(locator)]
|
21
|
-
button += descendant(:button)[attr(:id).equals(locator) | attr(:value).is(locator) |
|
17
|
+
button += descendant(:button)[attr(:id).equals(locator) | attr(:value).is(locator) | string.n.is(locator)]
|
22
18
|
button += descendant(:input)[attr(:type).equals('image')][attr(:alt).is(locator)]
|
23
19
|
end
|
24
20
|
|
@@ -86,7 +82,7 @@ module XPath
|
|
86
82
|
end
|
87
83
|
|
88
84
|
def table_rows(rows)
|
89
|
-
row_conditions = descendant(:tr)[table_row(rows.first)]
|
85
|
+
row_conditions = descendant(:tr)[table_row(rows.first)]
|
90
86
|
rows.drop(1).each do |row|
|
91
87
|
row_conditions = row_conditions.next_sibling(:tr)[table_row(row)]
|
92
88
|
end
|
@@ -94,21 +90,13 @@ module XPath
|
|
94
90
|
end
|
95
91
|
|
96
92
|
def table_row(cells)
|
97
|
-
cell_conditions = child(:td, :th)[text.equals(cells.first)]
|
93
|
+
cell_conditions = child(:td, :th)[text.equals(cells.first)]
|
98
94
|
cells.drop(1).each do |cell|
|
99
95
|
cell_conditions = cell_conditions.next_sibling(:td, :th)[text.equals(cell)]
|
100
96
|
end
|
101
97
|
cell_conditions
|
102
98
|
end
|
103
99
|
|
104
|
-
def wrap(path)
|
105
|
-
if path.respond_to?(:to_xpaths)
|
106
|
-
path.to_xpaths
|
107
|
-
else
|
108
|
-
[path.to_s].flatten
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
100
|
protected
|
113
101
|
|
114
102
|
def locate_field(xpath, locator)
|
data/lib/xpath/version.rb
CHANGED
data/spec/fixtures/form.html
CHANGED
@@ -1,5 +1,62 @@
|
|
1
1
|
<h1>Form</h1>
|
2
2
|
|
3
|
+
<p>
|
4
|
+
<a id="awesome-link" href="#link">An awesome link</a>
|
5
|
+
<a id="some-id" href="#id">With id</a>
|
6
|
+
<a title="My title" href="#title">A cool title</a>
|
7
|
+
<a href="#image"><img src="foo.png" alt="Alt link"/></a>
|
8
|
+
<a title="This title is too long" href="#bar">A link at a time</a>
|
9
|
+
<a title="This title" href="#foo">A link</a>
|
10
|
+
<a href="#bar"><img src="foo.png" alt="An image that is beautiful"/></a>
|
11
|
+
<a href="#foo"><img src="foo.ong" alt="An image"/></a>
|
12
|
+
<a>Wrong Link</a>
|
13
|
+
<a href="#spacey"> My
|
14
|
+
|
15
|
+
whitespaced
|
16
|
+
link</a>
|
17
|
+
<a href="#has-children">
|
18
|
+
An <em>emphatic</em> link with some children
|
19
|
+
</a>
|
20
|
+
</p>
|
21
|
+
|
22
|
+
<p>
|
23
|
+
<input type="submit" id="submit-with-id" data="id-submit" value="Has ID"/>
|
24
|
+
<input type="submit" value="submit-with-value" data="value-submit"/>
|
25
|
+
<input type="submit" value="not exact value submit" data="not-exact-value-submit"/>
|
26
|
+
<input type="submit" value="exact value submit" data="exact-value-submit"/>
|
27
|
+
|
28
|
+
<input type="button" id="button-with-id" data="id-button" value="Has ID"/>
|
29
|
+
<input type="button" value="button-with-value" data="value-button"/>
|
30
|
+
<input type="button" value="not exact value button" data="not-exact-value-button"/>
|
31
|
+
<input type="button" value="exact value button" data="exact-value-button"/>
|
32
|
+
|
33
|
+
<input type="image" id="imgbut-with-id" data="id-imgbut" value="Has ID"/>
|
34
|
+
<input type="image" value="imgbut-with-value" data="value-imgbut"/>
|
35
|
+
<input type="image" alt="imgbut-with-alt" data="alt-imgbut"/>
|
36
|
+
<input type="image" value="not exact value imgbut" data="not-exact-value-imgbut"/>
|
37
|
+
<input type="image" value="exact value imgbut" data="exact-value-imgbut"/>
|
38
|
+
|
39
|
+
<button id="btag-with-id" data="id-btag" value="Has ID"/>
|
40
|
+
<button value="btag-with-value" data="value-btag"/>
|
41
|
+
<button value="not exact value btag" data="not-exact-value-btag"/>
|
42
|
+
<button value="exact value btag" data="exact-value-btag"/>
|
43
|
+
|
44
|
+
<button data="text-btag">btag-with-text</button>
|
45
|
+
<button data="not-exact-text-btag">not exact text btag</button>
|
46
|
+
<button data="exact-text-btag">exact text btag</button>
|
47
|
+
|
48
|
+
<button data="btag-with-whitespace"> My
|
49
|
+
|
50
|
+
whitespaced
|
51
|
+
button</button>
|
52
|
+
<button href="btag-with-children">
|
53
|
+
An <em>emphatic</em> button with some children
|
54
|
+
</button>
|
55
|
+
|
56
|
+
<input type="schmoo" value="schmoo button" data="schmoo"/>
|
57
|
+
</p>
|
58
|
+
|
59
|
+
|
3
60
|
<form action="/form" method="post">
|
4
61
|
|
5
62
|
<p>
|
@@ -32,7 +89,7 @@
|
|
32
89
|
<label for="form_last_name">Last Name</label>
|
33
90
|
<input type="text" name="form[last_name]" value="Smith" id="form_last_name"/>
|
34
91
|
</p>
|
35
|
-
|
92
|
+
|
36
93
|
<p>
|
37
94
|
<label for="form_name_explanation">Explanation of Name</label>
|
38
95
|
<textarea name="form[name_explanation]" id="form_name_explanation"></textarea>
|
@@ -47,7 +104,7 @@
|
|
47
104
|
<label for="form_schmooo">Schmooo</label>
|
48
105
|
<input type="schmooo" name="form[schmooo]" value="This is Schmooo!" id="form_schmooo"/>
|
49
106
|
</p>
|
50
|
-
|
107
|
+
|
51
108
|
<p>
|
52
109
|
<label>Street<br/>
|
53
110
|
<input type="text" name="form[street]" value="Sesame street 66"/>
|
@@ -74,7 +131,7 @@
|
|
74
131
|
<label for="form_image">Image</label>
|
75
132
|
<input type="file" name="form[image]" id="form_image"/>
|
76
133
|
</p>
|
77
|
-
|
134
|
+
|
78
135
|
<p>
|
79
136
|
<input type="hidden" name="form[token]" value="12345" id="form_token"/>
|
80
137
|
</p>
|
@@ -196,7 +253,7 @@
|
|
196
253
|
<label for="form_document">Document</label>
|
197
254
|
<input type="file" name="form[document]" id="form_document"/>
|
198
255
|
</p>
|
199
|
-
|
256
|
+
|
200
257
|
<p>
|
201
258
|
<input type="submit" value="Upload"/>
|
202
259
|
<p>
|
@@ -229,7 +286,7 @@
|
|
229
286
|
<label for="html5_color">Html5 Color</label>
|
230
287
|
<input type="color" name="form[html5_color]" value="#FFF" id="html5_color"/>
|
231
288
|
</p>
|
232
|
-
|
289
|
+
|
233
290
|
<p>
|
234
291
|
<input type="submit" name="form[html5_submit]" value="html5_submit"/>
|
235
292
|
</p>
|
data/spec/fixtures/simple.html
CHANGED
data/spec/html_spec.rb
ADDED
@@ -0,0 +1,195 @@
|
|
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
|
+
|
9
|
+
def get(xpath)
|
10
|
+
all(xpath).first
|
11
|
+
end
|
12
|
+
|
13
|
+
def all(xpath)
|
14
|
+
xpath.to_xpaths.map do |xpath|
|
15
|
+
doc.xpath(xpath)
|
16
|
+
end.flatten
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#link' do
|
20
|
+
it "finds links by id" do
|
21
|
+
get(XPath::HTML.link('some-id'))[:href].should == '#id'
|
22
|
+
end
|
23
|
+
|
24
|
+
it "finds links by content" do
|
25
|
+
get(XPath::HTML.link('An awesome link'))[:href].should == '#link'
|
26
|
+
end
|
27
|
+
|
28
|
+
it "finds links by content without caring about whitespace" do
|
29
|
+
get(XPath::HTML.link('My whitespaced link'))[:href].should == '#spacey'
|
30
|
+
end
|
31
|
+
|
32
|
+
it "finds links with child tags by content" do
|
33
|
+
get(XPath::HTML.link('An emphatic link'))[:href].should == '#has-children'
|
34
|
+
get(XPath::HTML.link('emphatic'))[:href].should == '#has-children'
|
35
|
+
end
|
36
|
+
|
37
|
+
it "finds links by approximate content" do
|
38
|
+
get(XPath::HTML.link('awesome'))[:href].should == '#link'
|
39
|
+
end
|
40
|
+
|
41
|
+
it "prefers exact matches of content" do
|
42
|
+
result = all(XPath::HTML.link('A link'))
|
43
|
+
result[0][:href].should == '#foo'
|
44
|
+
result[1][:href].should == '#bar'
|
45
|
+
end
|
46
|
+
|
47
|
+
it "finds links by title" do
|
48
|
+
get(XPath::HTML.link('My title'))[:href].should == '#title'
|
49
|
+
end
|
50
|
+
|
51
|
+
it "finds links by approximate title" do
|
52
|
+
get(XPath::HTML.link('title'))[:href].should == '#title'
|
53
|
+
end
|
54
|
+
|
55
|
+
it "prefers exact matches of title" do
|
56
|
+
result = all(XPath::HTML.link('This title'))
|
57
|
+
result[0][:href].should == '#foo'
|
58
|
+
result[1][:href].should == '#bar'
|
59
|
+
end
|
60
|
+
|
61
|
+
it "finds links by an image's alt attribute" do
|
62
|
+
get(XPath::HTML.link('Alt link'))[:href].should == '#image'
|
63
|
+
end
|
64
|
+
|
65
|
+
it "finds links by an image's approximate alt attribute" do
|
66
|
+
get(XPath::HTML.link('Alt'))[:href].should == '#image'
|
67
|
+
end
|
68
|
+
|
69
|
+
it "prefers exact matches of image's alt attribute" do
|
70
|
+
result = all(XPath::HTML.link('An image'))
|
71
|
+
result[0][:href].should == '#foo'
|
72
|
+
result[1][:href].should == '#bar'
|
73
|
+
end
|
74
|
+
|
75
|
+
it "does not find links without href attriutes" do
|
76
|
+
get(XPath::HTML.link('Wrong Link')).should be_nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe '#button' do
|
81
|
+
context "with submit type" do
|
82
|
+
it "finds buttons by id" do
|
83
|
+
get(XPath::HTML.button('submit-with-id'))[:data].should == 'id-submit'
|
84
|
+
end
|
85
|
+
|
86
|
+
it "finds buttons by value" do
|
87
|
+
get(XPath::HTML.button('submit-with-value'))[:data].should == 'value-submit'
|
88
|
+
end
|
89
|
+
|
90
|
+
it "finds buttons by approximate value " do
|
91
|
+
get(XPath::HTML.button('mit-with-val'))[:data].should == 'value-submit'
|
92
|
+
end
|
93
|
+
|
94
|
+
it "finds prefers buttons with exact value " do
|
95
|
+
results = all(XPath::HTML.button('exact value submit'))
|
96
|
+
results[0][:data].should == 'exact-value-submit'
|
97
|
+
results[1][:data].should == 'not-exact-value-submit'
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context "with button type" do
|
102
|
+
it "finds buttons by id" do
|
103
|
+
get(XPath::HTML.button('button-with-id'))[:data].should == 'id-button'
|
104
|
+
end
|
105
|
+
|
106
|
+
it "finds buttons by value" do
|
107
|
+
get(XPath::HTML.button('button-with-value'))[:data].should == 'value-button'
|
108
|
+
end
|
109
|
+
|
110
|
+
it "finds buttons by approximate value " do
|
111
|
+
get(XPath::HTML.button('ton-with-val'))[:data].should == 'value-button'
|
112
|
+
end
|
113
|
+
|
114
|
+
it "finds prefers buttons with exact value " do
|
115
|
+
results = all(XPath::HTML.button('exact value button'))
|
116
|
+
results[0][:data].should == 'exact-value-button'
|
117
|
+
results[1][:data].should == 'not-exact-value-button'
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context "with image type" do
|
122
|
+
it "finds buttons by id" do
|
123
|
+
get(XPath::HTML.button('imgbut-with-id'))[:data].should == 'id-imgbut'
|
124
|
+
end
|
125
|
+
|
126
|
+
it "finds buttons by value" do
|
127
|
+
get(XPath::HTML.button('imgbut-with-value'))[:data].should == 'value-imgbut'
|
128
|
+
end
|
129
|
+
|
130
|
+
it "finds buttons by approximate value " do
|
131
|
+
get(XPath::HTML.button('gbut-with-val'))[:data].should == 'value-imgbut'
|
132
|
+
end
|
133
|
+
|
134
|
+
it "finds buttons by alt attribute" do
|
135
|
+
get(XPath::HTML.button('imgbut-with-alt'))[:data].should == 'alt-imgbut'
|
136
|
+
end
|
137
|
+
|
138
|
+
it "prefers buttons with exact value " do
|
139
|
+
results = all(XPath::HTML.button('exact value imgbut'))
|
140
|
+
results[0][:data].should == 'exact-value-imgbut'
|
141
|
+
results[1][:data].should == 'not-exact-value-imgbut'
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context "with button tag" do
|
146
|
+
it "finds buttons by id" do
|
147
|
+
get(XPath::HTML.button('btag-with-id'))[:data].should == 'id-btag'
|
148
|
+
end
|
149
|
+
|
150
|
+
it "finds buttons by value" do
|
151
|
+
get(XPath::HTML.button('btag-with-value'))[:data].should == 'value-btag'
|
152
|
+
end
|
153
|
+
|
154
|
+
it "finds buttons by approximate value " do
|
155
|
+
get(XPath::HTML.button('tag-with-val'))[:data].should == 'value-btag'
|
156
|
+
end
|
157
|
+
|
158
|
+
it "finds prefers buttons with exact value " do
|
159
|
+
results = all(XPath::HTML.button('exact value btag'))
|
160
|
+
results[0][:data].should == 'exact-value-btag'
|
161
|
+
results[1][:data].should == 'not-exact-value-btag'
|
162
|
+
end
|
163
|
+
|
164
|
+
it "finds buttons by text" do
|
165
|
+
get(XPath::HTML.button('btag-with-text'))[:data].should == 'text-btag'
|
166
|
+
end
|
167
|
+
|
168
|
+
it "finds buttons by text ignoring whitespace" do
|
169
|
+
get(XPath::HTML.button('My whitespaced button'))[:data].should == 'btag-with-whitespace'
|
170
|
+
end
|
171
|
+
|
172
|
+
it "finds buttons by approximate text " do
|
173
|
+
get(XPath::HTML.button('tag-with-tex'))[:data].should == 'text-btag'
|
174
|
+
end
|
175
|
+
|
176
|
+
it "finds buttons with child tags by text" do
|
177
|
+
get(XPath::HTML.button('An emphatic button'))[:href].should == 'btag-with-children'
|
178
|
+
get(XPath::HTML.button('emphatic'))[:href].should == 'btag-with-children'
|
179
|
+
end
|
180
|
+
|
181
|
+
it "prefers buttons with exact text" do
|
182
|
+
results = all(XPath::HTML.button('exact text btag'))
|
183
|
+
results[0][:data].should == 'exact-text-btag'
|
184
|
+
results[1][:data].should == 'not-exact-text-btag'
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
context "with unkown type" do
|
189
|
+
it "does not find the button" do
|
190
|
+
get(XPath::HTML.button('schmoo button')).should be_nil
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
195
|
+
end
|
data/spec/xpath_spec.rb
CHANGED
@@ -43,6 +43,12 @@ describe XPath do
|
|
43
43
|
@results[0].text.should == 'Blah'
|
44
44
|
@results[3].text.should == 'A list'
|
45
45
|
end
|
46
|
+
|
47
|
+
it "should find all nodes when no arguments given" do
|
48
|
+
@results = xpath { |x| x.descendant[x.attr(:id) == 'foo'].descendant }
|
49
|
+
@results[0].text.should == 'Blah'
|
50
|
+
@results[4].text.should == 'A list'
|
51
|
+
end
|
46
52
|
end
|
47
53
|
|
48
54
|
describe '#child' do
|
@@ -62,6 +68,12 @@ describe XPath do
|
|
62
68
|
@results[0].text.should == 'Blah'
|
63
69
|
@results[3].text.should == 'A list'
|
64
70
|
end
|
71
|
+
|
72
|
+
it "should find all nodes when no arguments given" do
|
73
|
+
@results = xpath { |x| x.descendant[x.attr(:id) == 'foo'].child }
|
74
|
+
@results[0].text.should == 'Blah'
|
75
|
+
@results[3].text.should == 'A list'
|
76
|
+
end
|
65
77
|
end
|
66
78
|
|
67
79
|
describe '#next_sibling' do
|
@@ -70,6 +82,7 @@ describe XPath do
|
|
70
82
|
xpath { |x| x.descendant(:p)[x.attr(:id) == 'fooDiv'].next_sibling(:ul, :p) }.first.text.should == 'Bax'
|
71
83
|
xpath { |x| x.descendant(:p)[x.attr(:title) == 'monkey'].next_sibling(:ul, :p) }.first.text.should == 'A list'
|
72
84
|
xpath { |x| x.descendant(:p)[x.attr(:id) == 'fooDiv'].next_sibling(:ul, :li) }.first.should be_nil
|
85
|
+
xpath { |x| x.descendant(:p)[x.attr(:id) == 'fooDiv'].next_sibling }.first.text.should == 'Bax'
|
73
86
|
end
|
74
87
|
end
|
75
88
|
|
@@ -221,7 +234,7 @@ describe XPath do
|
|
221
234
|
@results[0][:title].should == "barDiv"
|
222
235
|
@results[1][:title].should == "fooDiv"
|
223
236
|
end
|
224
|
-
|
237
|
+
|
225
238
|
it "should be closed" do
|
226
239
|
@results = xpath do |x|
|
227
240
|
foo_div = x.anywhere(:div).where(x.attr(:id) == 'foo')
|
@@ -231,6 +244,27 @@ describe XPath do
|
|
231
244
|
end
|
232
245
|
end
|
233
246
|
|
247
|
+
describe '#css' do
|
248
|
+
it "should find nodes by the given CSS selector" do
|
249
|
+
@results = xpath { |x| x.css('#preference p') }
|
250
|
+
@results[0].text.should == 'allamas'
|
251
|
+
@results[1].text.should == 'llama'
|
252
|
+
end
|
253
|
+
|
254
|
+
it "should respect previous expression" do
|
255
|
+
@results = xpath { |x| x.descendant[x.attr(:id) == 'moar'].css('p') }
|
256
|
+
@results[0].text.should == 'chimp'
|
257
|
+
@results[1].text.should == 'flamingo'
|
258
|
+
end
|
259
|
+
|
260
|
+
it "should allow comma separated selectors" do
|
261
|
+
@results = xpath { |x| x.descendant[x.attr(:id) == 'moar'].css('div, p') }
|
262
|
+
@results[0].text.should == 'chimp'
|
263
|
+
@results[1].text.should == 'flamingo'
|
264
|
+
@results[2].text.should == 'elephant'
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
234
268
|
describe '#name' do
|
235
269
|
it "should match the node's name" do
|
236
270
|
xpath { |x| x.descendant(:*).where(x.name == 'ul') }.first.text.should == "A list"
|
@@ -250,7 +284,11 @@ describe XPath do
|
|
250
284
|
|
251
285
|
it "should raise an argument error if the interpolation key is not given" do
|
252
286
|
@xpath = XPath.generate { |x| x.descendant(:*).where(x.attr(:id) == x.var(:id).string_literal) }
|
253
|
-
|
287
|
+
if defined?(KeyError)
|
288
|
+
lambda { @xpath.apply.to_xpath }.should raise_error(KeyError)
|
289
|
+
else
|
290
|
+
lambda { @xpath.apply.to_xpath }.should raise_error(ArgumentError)
|
291
|
+
end
|
254
292
|
end
|
255
293
|
end
|
256
294
|
|
@@ -274,8 +312,8 @@ describe XPath do
|
|
274
312
|
|
275
313
|
describe '#union' do
|
276
314
|
it "should create a union expression" do
|
277
|
-
@expr1 = XPath.generate { |x| x.descendant(:p) }
|
278
|
-
@expr2 = XPath.generate { |x| x.descendant(:div) }
|
315
|
+
@expr1 = XPath.generate { |x| x.descendant(:p) }
|
316
|
+
@expr2 = XPath.generate { |x| x.descendant(:div) }
|
279
317
|
@collection = @expr1.union(@expr2)
|
280
318
|
@xpath1 = @collection.where(XPath.attr(:id) == 'foo').to_xpath
|
281
319
|
@xpath2 = @collection.where(XPath.attr(:id) == XPath.varstring(:id)).apply(:id => 'fooDiv').to_xpath
|
@@ -286,8 +324,8 @@ describe XPath do
|
|
286
324
|
end
|
287
325
|
|
288
326
|
it "should be aliased as +" do
|
289
|
-
@expr1 = XPath.generate { |x| x.descendant(:p) }
|
290
|
-
@expr2 = XPath.generate { |x| x.descendant(:div) }
|
327
|
+
@expr1 = XPath.generate { |x| x.descendant(:p) }
|
328
|
+
@expr2 = XPath.generate { |x| x.descendant(:div) }
|
291
329
|
@collection = @expr1 + @expr2
|
292
330
|
@xpath1 = @collection.where(XPath.attr(:id) == 'foo').to_xpath
|
293
331
|
@xpath2 = @collection.where(XPath.attr(:id) == XPath.varstring(:id)).apply(:id => 'fooDiv').to_xpath
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xpath
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Jonas Nicklas
|
@@ -15,39 +15,38 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-10-06 00:00:00 +02:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
|
-
name:
|
22
|
+
name: nokogiri
|
23
23
|
prerelease: false
|
24
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
25
|
none: false
|
26
26
|
requirements:
|
27
|
-
- -
|
27
|
+
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
hash:
|
29
|
+
hash: 9
|
30
30
|
segments:
|
31
31
|
- 1
|
32
|
-
-
|
33
|
-
|
34
|
-
|
35
|
-
type: :development
|
32
|
+
- 3
|
33
|
+
version: "1.3"
|
34
|
+
type: :runtime
|
36
35
|
version_requirements: *id001
|
37
36
|
- !ruby/object:Gem::Dependency
|
38
|
-
name:
|
37
|
+
name: rspec
|
39
38
|
prerelease: false
|
40
39
|
requirement: &id002 !ruby/object:Gem::Requirement
|
41
40
|
none: false
|
42
41
|
requirements:
|
43
42
|
- - ">="
|
44
43
|
- !ruby/object:Gem::Version
|
45
|
-
hash:
|
44
|
+
hash: 13
|
46
45
|
segments:
|
47
46
|
- 1
|
48
|
-
-
|
49
|
-
-
|
50
|
-
version: 1.
|
47
|
+
- 2
|
48
|
+
- 9
|
49
|
+
version: 1.2.9
|
51
50
|
type: :development
|
52
51
|
version_requirements: *id002
|
53
52
|
- !ruby/object:Gem::Dependency
|
@@ -84,6 +83,7 @@ files:
|
|
84
83
|
- spec/fixtures/form.html
|
85
84
|
- spec/fixtures/simple.html
|
86
85
|
- spec/fixtures/stuff.html
|
86
|
+
- spec/html_spec.rb
|
87
87
|
- spec/spec_helper.rb
|
88
88
|
- spec/union_spec.rb
|
89
89
|
- spec/xpath_spec.rb
|