xpath 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +30 -3
- data/lib/xpath/expression.rb +6 -5
- data/lib/xpath/html.rb +16 -11
- data/lib/xpath/version.rb +1 -1
- data/spec/fixtures/form.html +18 -0
- data/spec/fixtures/simple.html +1 -1
- data/spec/html_spec.rb +33 -12
- data/spec/xpath_spec.rb +8 -2
- metadata +3 -3
data/README.rdoc
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
= XPath
|
2
2
|
|
3
|
-
XPath is a Ruby DSL around a subset of XPath 1.0. It's primary purpose is to
|
3
|
+
XPath is a Ruby DSL around a subset of XPath 1.0. It's primary purpose is to
|
4
|
+
facilitate writing complex XPath queries from Ruby code.
|
4
5
|
|
5
6
|
== Generating expressions
|
6
7
|
|
@@ -8,7 +9,8 @@ To create quick, one of expressions, XPath.generate can be used:
|
|
8
9
|
|
9
10
|
XPath.generate { |x| x.descendant(:ul)[x.attr(:id) == 'foo'] }
|
10
11
|
|
11
|
-
However for more complex expressions, it is probably ore convenient to include
|
12
|
+
However for more complex expressions, it is probably ore convenient to include
|
13
|
+
the XPath module into your own class or module:
|
12
14
|
|
13
15
|
module MyXPaths
|
14
16
|
include XPath
|
@@ -22,8 +24,33 @@ However for more complex expressions, it is probably ore convenient to include t
|
|
22
24
|
end
|
23
25
|
end
|
24
26
|
|
25
|
-
Both ways return an XPath::Expression instance, which can be further modified.
|
27
|
+
Both ways return an XPath::Expression instance, which can be further modified.
|
28
|
+
To convert the expression to a string, just call #to_s on it.
|
26
29
|
|
27
30
|
== HTML
|
28
31
|
|
29
32
|
XPath comes with a set of premade XPaths for use with HTML documents.
|
33
|
+
|
34
|
+
== License
|
35
|
+
|
36
|
+
(The MIT License)
|
37
|
+
|
38
|
+
Copyright © 2010 Jonas Nicklas
|
39
|
+
|
40
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
41
|
+
this software and associated documentation files (the ‘Software’), to deal in
|
42
|
+
the Software without restriction, including without limitation the rights to
|
43
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
44
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
45
|
+
so, subject to the following conditions:
|
46
|
+
|
47
|
+
The above copyright notice and this permission notice shall be included in all
|
48
|
+
copies or substantial portions of the Software.
|
49
|
+
|
50
|
+
THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
51
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
52
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
53
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
54
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
55
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
56
|
+
SOFTWARE.
|
data/lib/xpath/expression.rb
CHANGED
@@ -126,14 +126,15 @@ module XPath
|
|
126
126
|
end
|
127
127
|
|
128
128
|
def to_xpath(predicate=nil)
|
129
|
-
|
130
|
-
|
131
|
-
|
129
|
+
string = @expression
|
130
|
+
string = @expression.to_xpath(predicate) unless @expression.is_a?(String)
|
131
|
+
if string.include?("'")
|
132
|
+
string = string.split("'", -1).map do |substr|
|
132
133
|
"'#{substr}'"
|
133
134
|
end.join(%q{,"'",})
|
134
|
-
"concat(#{
|
135
|
+
"concat(#{string})"
|
135
136
|
else
|
136
|
-
"'#{
|
137
|
+
"'#{string}'"
|
137
138
|
end
|
138
139
|
end
|
139
140
|
end
|
data/lib/xpath/html.rb
CHANGED
@@ -3,8 +3,9 @@ module XPath
|
|
3
3
|
include XPath
|
4
4
|
extend self
|
5
5
|
|
6
|
-
def link(locator)
|
7
|
-
|
6
|
+
def link(locator, options={})
|
7
|
+
href = options[:href]
|
8
|
+
link = descendant(:a)[href ? attr(:href).equals(href) : attr(:href)]
|
8
9
|
link[attr(:id).equals(locator) | string.n.is(locator) | attr(:title).is(locator) | descendant(:img)[attr(:alt).is(locator)]]
|
9
10
|
end
|
10
11
|
|
@@ -23,7 +24,7 @@ module XPath
|
|
23
24
|
end
|
24
25
|
|
25
26
|
def fieldset(locator)
|
26
|
-
descendant(:fieldset)[attr(:id).equals(locator) | descendant(:legend)[
|
27
|
+
descendant(:fieldset)[attr(:id).equals(locator) | descendant(:legend)[string.n.is(locator)]]
|
27
28
|
end
|
28
29
|
|
29
30
|
def field(locator, options={})
|
@@ -46,11 +47,11 @@ module XPath
|
|
46
47
|
xpath = locate_field(descendant(:select), locator)
|
47
48
|
|
48
49
|
options[:options].each do |option|
|
49
|
-
xpath = xpath[descendant(:option).
|
50
|
+
xpath = xpath[descendant(:option).equals(option)]
|
50
51
|
end if options[:options]
|
51
52
|
|
52
53
|
[options[:selected]].flatten.each do |option|
|
53
|
-
xpath = xpath[descendant(:option)[attr(:selected)].
|
54
|
+
xpath = xpath[descendant(:option)[attr(:selected)].equals(option)]
|
54
55
|
end if options[:selected]
|
55
56
|
|
56
57
|
xpath
|
@@ -68,8 +69,12 @@ module XPath
|
|
68
69
|
locate_field(descendant(:input)[attr(:type).equals('file')], locator)
|
69
70
|
end
|
70
71
|
|
72
|
+
def optgroup(name)
|
73
|
+
descendant(:optgroup)[attr(:label).is(name)]
|
74
|
+
end
|
75
|
+
|
71
76
|
def option(name)
|
72
|
-
descendant(:option)[
|
77
|
+
descendant(:option)[string.n.is(name)]
|
73
78
|
end
|
74
79
|
|
75
80
|
def table(locator, options={})
|
@@ -87,9 +92,9 @@ module XPath
|
|
87
92
|
end
|
88
93
|
|
89
94
|
def table_row(cells)
|
90
|
-
cell_conditions = child(:td, :th)[
|
95
|
+
cell_conditions = child(:td, :th)[string.n.equals(cells.first)]
|
91
96
|
cells.drop(1).each do |cell|
|
92
|
-
cell_conditions = cell_conditions.next_sibling(:td, :th)[
|
97
|
+
cell_conditions = cell_conditions.next_sibling(:td, :th)[string.n.equals(cell)]
|
93
98
|
end
|
94
99
|
cell_conditions
|
95
100
|
end
|
@@ -97,12 +102,12 @@ module XPath
|
|
97
102
|
protected
|
98
103
|
|
99
104
|
def locate_field(xpath, locator)
|
100
|
-
locate_field = xpath[attr(:id).equals(locator) | attr(:name).equals(locator) | attr(:id).equals(anywhere(:label)[
|
101
|
-
locate_field += descendant(:label)[
|
105
|
+
locate_field = xpath[attr(:id).equals(locator) | attr(:name).equals(locator) | attr(:id).equals(anywhere(:label)[string.n.is(locator)].attr(:for))]
|
106
|
+
locate_field += descendant(:label)[string.n.is(locator)].descendant(xpath)
|
102
107
|
end
|
103
108
|
|
104
109
|
def field_value(value)
|
105
|
-
(
|
110
|
+
(string.n.is(value) & tag(:textarea)) | (attr(:value).equals(value) & ~tag(:textarea))
|
106
111
|
end
|
107
112
|
|
108
113
|
end
|
data/lib/xpath/version.rb
CHANGED
data/spec/fixtures/form.html
CHANGED
@@ -9,6 +9,7 @@
|
|
9
9
|
<a title="This title" data="link-exact" href="#foo">A link</a>
|
10
10
|
<a href="#bar" data="link-img-fuzzy"><img src="foo.png" alt="An image that is beautiful"/></a>
|
11
11
|
<a href="#foo" data="link-img-exact"><img src="foo.ong" alt="An image"/></a>
|
12
|
+
<a href="http://www.example.com" data="link-href">Href-ed link</a>
|
12
13
|
<a>Wrong Link</a>
|
13
14
|
<a href="#spacey" data="link-whitespace"> My
|
14
15
|
|
@@ -54,15 +55,32 @@
|
|
54
55
|
</button>
|
55
56
|
|
56
57
|
<input type="schmoo" value="schmoo button" data="schmoo"/>
|
58
|
+
<label for="text-with-id">Label text <input type="text" id="text-with-id" data="id-text" value="monkey"/></label>
|
59
|
+
<label for="problem-text-with-id">Label text's got an apostrophe <input type="text" id="problem-text-with-id" data="id-problem-text" value="monkey"/></label>
|
57
60
|
</p>
|
58
61
|
|
59
62
|
<p>
|
60
63
|
<fieldset id="some-fieldset-id" data="fieldset-id"></fieldset>
|
61
64
|
<fieldset data="fieldset-legend"><legend>Some Legend</legend></fieldset>
|
65
|
+
<fieldset data="fieldset-legend-span"><legend><span>Span Legend</span></legend></fieldset>
|
62
66
|
<fieldset data="fieldset-fuzzy"><legend>Long legend yo</legend></fieldset>
|
63
67
|
<fieldset data="fieldset-exact"><legend>Long legend</legend></fieldset>
|
64
68
|
</p>
|
65
69
|
|
66
70
|
<p>
|
71
|
+
<select>
|
72
|
+
<optgroup label="Group A" data="optgroup-a"></optgroup>
|
73
|
+
<optgroup label="Group B" data="optgroup-b"></optgroup>
|
74
|
+
</select>
|
75
|
+
</p>
|
67
76
|
|
77
|
+
<p>
|
78
|
+
<table id="whitespaced-table" data="table-with-whitespace">
|
79
|
+
<tr>
|
80
|
+
<td data="cell-whitespaced">I have
|
81
|
+
<span>nested whitespace</span>
|
82
|
+
</td>
|
83
|
+
<td>I don't</td>
|
84
|
+
</tr>
|
85
|
+
</table>
|
68
86
|
</p>
|
data/spec/fixtures/simple.html
CHANGED
data/spec/html_spec.rb
CHANGED
@@ -33,6 +33,8 @@ describe XPath::HTML do
|
|
33
33
|
it("finds links by image's approximate alt attribute") { get('Alt').should == 'link-img' }
|
34
34
|
it("prefers exact matches of image's alt attribute") { all('An image').should == ['link-img-exact', 'link-img-fuzzy'] }
|
35
35
|
it("does not find links without href attriutes") { get('Wrong Link').should be_nil }
|
36
|
+
it("finds links with an href") { get("Href-ed link", :href => 'http://www.example.com').should == 'link-href' }
|
37
|
+
it("does not find links with an incorrect href") { get("Href-ed link", :href => 'http://www.somewhere.com').should be_nil }
|
36
38
|
end
|
37
39
|
|
38
40
|
describe '#button' do
|
@@ -81,10 +83,11 @@ describe XPath::HTML do
|
|
81
83
|
describe '#fieldset' do
|
82
84
|
subject { :fieldset }
|
83
85
|
|
84
|
-
it("finds fieldsets by id")
|
85
|
-
it("finds fieldsets by legend")
|
86
|
-
it("
|
87
|
-
it("
|
86
|
+
it("finds fieldsets by id") { get('some-fieldset-id').should == 'fieldset-id' }
|
87
|
+
it("finds fieldsets by legend") { get('Some Legend').should == 'fieldset-legend' }
|
88
|
+
it("finds fieldsets by legend child tags") { get('Span Legend').should == 'fieldset-legend-span' }
|
89
|
+
it("accepts approximate legends") { get('Legend').should == 'fieldset-legend' }
|
90
|
+
it("prefers exact legend") { all('Long legend').should == ['fieldset-exact', 'fieldset-fuzzy'] }
|
88
91
|
end
|
89
92
|
|
90
93
|
describe '#field' do
|
@@ -124,14 +127,15 @@ describe XPath::HTML do
|
|
124
127
|
end
|
125
128
|
|
126
129
|
context "by parent label" do
|
127
|
-
it("finds inputs with text type")
|
128
|
-
it("finds inputs
|
129
|
-
it("finds inputs with
|
130
|
-
it("finds
|
131
|
-
it("finds
|
132
|
-
it("
|
133
|
-
it("does not find
|
134
|
-
it("does not find
|
130
|
+
it("finds inputs with text type") { get('Label text').should == 'id-text' }
|
131
|
+
it("finds inputs where label has problem chars") { get("Label text's got an apostrophe").should == 'id-problem-text' }
|
132
|
+
it("finds inputs with password type") {}
|
133
|
+
it("finds inputs with custom type") {}
|
134
|
+
it("finds textareas") {}
|
135
|
+
it("finds select boxes") {}
|
136
|
+
it("does not find submit buttons") {}
|
137
|
+
it("does not find image buttons") {}
|
138
|
+
it("does not find hidden fields") {}
|
135
139
|
end
|
136
140
|
|
137
141
|
context "with :with option" do
|
@@ -158,6 +162,23 @@ describe XPath::HTML do
|
|
158
162
|
end
|
159
163
|
|
160
164
|
describe '#fillable_field' do
|
165
|
+
subject{ :fillable_field }
|
166
|
+
context "by parent label" do
|
167
|
+
it("finds inputs with text type") { get('Label text').should == 'id-text' }
|
168
|
+
it("finds inputs where label has problem chars") { get("Label text's got an apostrophe").should == 'id-problem-text' }
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
describe "#optgroup" do
|
174
|
+
subject { :optgroup }
|
175
|
+
|
176
|
+
it("finds optgroups by label") { get('Group A').should == 'optgroup-a' }
|
177
|
+
end
|
178
|
+
|
179
|
+
describe "#table" do
|
180
|
+
subject {:table}
|
161
181
|
|
182
|
+
it("finds cell content regardless of whitespace") {get('whitespaced-table', :rows => [["I have nested whitespace", "I don't"]]).should == 'table-with-whitespace'}
|
162
183
|
end
|
163
184
|
end
|
data/spec/xpath_spec.rb
CHANGED
@@ -265,11 +265,17 @@ describe XPath do
|
|
265
265
|
@results[1].text.should == 'flamingo'
|
266
266
|
end
|
267
267
|
|
268
|
+
it "should be composable" do
|
269
|
+
@results = xpath { |x| x.css('#moar').descendant(:p) }
|
270
|
+
@results[0].text.should == 'chimp'
|
271
|
+
@results[1].text.should == 'flamingo'
|
272
|
+
end
|
273
|
+
|
268
274
|
it "should allow comma separated selectors" do
|
269
275
|
@results = xpath { |x| x.descendant[x.attr(:id) == 'moar'].css('div, p') }
|
270
276
|
@results[0].text.should == 'chimp'
|
271
|
-
@results[1].text.should == '
|
272
|
-
@results[2].text.should == '
|
277
|
+
@results[1].text.should == 'elephant'
|
278
|
+
@results[2].text.should == 'flamingo'
|
273
279
|
end
|
274
280
|
end
|
275
281
|
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
8
|
+
- 3
|
9
|
+
version: 0.1.3
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Jonas Nicklas
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date:
|
17
|
+
date: 2011-01-09 00:00:00 +01:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|