xpath 0.1.2 → 0.1.3
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/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
|