xpath 0.1.0 → 0.1.1
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.
- 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
|