pincers 0.5.2 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/pincers.rb +1 -0
- data/lib/pincers/backend/nokogiri.rb +0 -1
- data/lib/pincers/core/download.rb +1 -0
- data/lib/pincers/core/query.rb +72 -0
- data/lib/pincers/core/root_context.rb +1 -1
- data/lib/pincers/core/search_context.rb +8 -11
- data/lib/pincers/css/parser.rb +29 -0
- data/lib/pincers/css/xpath_visitor.rb +81 -0
- data/lib/pincers/extension/actions.rb +2 -2
- data/lib/pincers/extension/labs.rb +1 -1
- data/lib/pincers/extension/queries.rb +2 -15
- data/lib/pincers/version.rb +1 -1
- metadata +5 -3
- data/lib/pincers/support/query.rb +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b243194fe1a23f2a16a34c0e7007de2d9f4eecfc
|
4
|
+
data.tar.gz: b442f0a02e79c6fc600d1b50cff6187ffc0bc3d4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9814b3a3c6139e62e53fea8b0448173196e2fd657656b9cfb49aa7856fd12e0d312842de844284e481821d223af838abf25f547107f7c90db921e10ee5969554
|
7
|
+
data.tar.gz: d91861c7c3ab3b717acfc703f758de7ea09dd1a89d6394b5c80932c069b4710a3d17b0b16dbc8004378ed4ce1d6985a77b4d631f9b5f1055515de171738bb95c
|
data/lib/pincers.rb
CHANGED
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'pincers/css/parser'
|
2
|
+
require 'pincers/support/xpath_builder'
|
3
|
+
|
4
|
+
module Pincers::Core
|
5
|
+
class Query
|
6
|
+
|
7
|
+
def self.build_from_options(_backend, _selector, _options={}, &_block)
|
8
|
+
limit = _options.delete(:limit)
|
9
|
+
lang = :xpath
|
10
|
+
exp = nil
|
11
|
+
|
12
|
+
if _selector
|
13
|
+
parser = Pincers::CSS::Parser.new _selector
|
14
|
+
if parser.is_extended?
|
15
|
+
exp = parser.to_xpath('.//') # Should we use // for root?
|
16
|
+
exp = exp.first if exp.length == 1
|
17
|
+
else
|
18
|
+
lang = :css
|
19
|
+
exp = _selector
|
20
|
+
end
|
21
|
+
elsif _options[:xpath]
|
22
|
+
exp = _options[:xpath]
|
23
|
+
else
|
24
|
+
builder = Pincers::Support::XPathBuilder.new _options
|
25
|
+
_block.call builder unless _block.nil?
|
26
|
+
exp = builder.expression
|
27
|
+
end
|
28
|
+
|
29
|
+
self.new _backend, lang, exp, limit
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :lang, :query, :limit
|
33
|
+
|
34
|
+
def initialize(_backend, _lang, _query, _limit)
|
35
|
+
@backend = _backend
|
36
|
+
@lang = _lang
|
37
|
+
@query = _query
|
38
|
+
@limit = _limit
|
39
|
+
end
|
40
|
+
|
41
|
+
def execute(_elements, _force_limit=nil)
|
42
|
+
fun = case @lang
|
43
|
+
when :xpath then :search_by_xpath
|
44
|
+
else :search_by_css end
|
45
|
+
|
46
|
+
query_elements _elements, fun, _force_limit || @limit
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def query_elements(_elements, _fun, _limit)
|
52
|
+
elements = []
|
53
|
+
explode_elements(_elements) do |element, query|
|
54
|
+
limit = _limit.nil? ? nil : _limit - elements.count
|
55
|
+
elements += @backend.send(_fun, element, query, limit)
|
56
|
+
break if !_limit.nil? && elements.length >= _limit
|
57
|
+
end
|
58
|
+
elements
|
59
|
+
end
|
60
|
+
|
61
|
+
def explode_elements(_elements)
|
62
|
+
_elements.each do |element|
|
63
|
+
if @query.is_a? Array
|
64
|
+
@query.each { |q| yield element, q }
|
65
|
+
else
|
66
|
+
yield element, @query
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -116,7 +116,7 @@ module Pincers::Core
|
|
116
116
|
when :parent
|
117
117
|
backend.switch_to_parent_frame
|
118
118
|
when String
|
119
|
-
backend.switch_to_frame
|
119
|
+
backend.switch_to_frame search(_frame).element!
|
120
120
|
when SearchContext
|
121
121
|
backend.switch_to_frame _frame.element!
|
122
122
|
else
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'pincers/extension/queries'
|
2
2
|
require 'pincers/extension/actions'
|
3
3
|
require 'pincers/extension/labs'
|
4
|
-
require 'pincers/
|
4
|
+
require 'pincers/core/query'
|
5
5
|
|
6
6
|
module Pincers::Core
|
7
7
|
class SearchContext
|
@@ -91,18 +91,15 @@ module Pincers::Core
|
|
91
91
|
if elements.last.nil? then nil else wrap_siblings [elements.last] end
|
92
92
|
end
|
93
93
|
|
94
|
-
def
|
95
|
-
|
96
|
-
|
97
|
-
|
94
|
+
def search(_selector=nil, _options={}, &_block)
|
95
|
+
if _selector.is_a? Hash
|
96
|
+
_options = _selector
|
97
|
+
_selector = nil
|
98
98
|
end
|
99
|
-
end
|
100
99
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
wrap_childs query
|
105
|
-
end
|
100
|
+
query = Query.build_from_options(backend, _selector, _options, &_block)
|
101
|
+
|
102
|
+
wrap_errors { wrap_childs query }
|
106
103
|
end
|
107
104
|
|
108
105
|
def attribute(_name, _value=nil)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'pincers/css/xpath_visitor'
|
2
|
+
|
3
|
+
module Pincers::CSS
|
4
|
+
|
5
|
+
class Parser
|
6
|
+
|
7
|
+
IS_EXTENDED_RGX = /:(contains|has|first|last|even|odd|eq|gt|lt|button|checkbox|file|image|password|radio|reset|submit|text|selected|checked)([^\w\-]|$)/
|
8
|
+
|
9
|
+
attr_reader :selector
|
10
|
+
|
11
|
+
def is_extended?
|
12
|
+
IS_EXTENDED_RGX === selector
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(_selector)
|
16
|
+
@selector = _selector
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_xpath(_prefix='//')
|
20
|
+
# use nokogiri parser and our custom visitor
|
21
|
+
::Nokogiri::CSS.xpath_for @selector, {
|
22
|
+
prefix: _prefix,
|
23
|
+
visitor: XPathVisitor.new
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Pincers::CSS
|
2
|
+
|
3
|
+
class XPathVisitor < ::Nokogiri::CSS::XPathVisitor
|
4
|
+
|
5
|
+
# jQuery extended functions and classes
|
6
|
+
|
7
|
+
def visit_function_contains(_node) # override nokofiri impl to search in value attribute too
|
8
|
+
"(contains(., #{_node.value[1]}) or contains(@value, #{_node.value[1]}))"
|
9
|
+
end
|
10
|
+
|
11
|
+
def visit_function_has(_node)
|
12
|
+
_node.value[1].accept(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
def visit_function_eq(_node) # override nokogiri impl to make it zero-based
|
16
|
+
"(position()-1)=#{_node.value[1]}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def visit_function_gt(_node) # override nokogiri impl to make it zero-based
|
20
|
+
# "((#{_node.value[1]} >= 0 and position() > #{_node.value[1]}) or (#{_node.value[1]} < 0 and position() < #{_node.value[1]}))"
|
21
|
+
"(position()-1)>#{_node.value[1]}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def visit_function_lt(_node)
|
25
|
+
"(position()-1)<#{_node.value[1]}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def visit_pseudo_class_input(_node)
|
29
|
+
"((name()='input' and not(@type='hidden')) or name()='textarea' or name()='select' or name()='button')"
|
30
|
+
end
|
31
|
+
|
32
|
+
def visit_pseudo_class_button(_node)
|
33
|
+
"(name()='button' or (name()='input' and @type='button'))"
|
34
|
+
end
|
35
|
+
|
36
|
+
def visit_pseudo_class_checkbox(_node)
|
37
|
+
"@type='checkbox'"
|
38
|
+
end
|
39
|
+
|
40
|
+
def visit_pseudo_class_file(_node)
|
41
|
+
"@type='file'"
|
42
|
+
end
|
43
|
+
|
44
|
+
def visit_pseudo_class_image(_node)
|
45
|
+
"@type='image'"
|
46
|
+
end
|
47
|
+
|
48
|
+
def visit_pseudo_class_password(_node)
|
49
|
+
"@type='password'"
|
50
|
+
end
|
51
|
+
|
52
|
+
def visit_pseudo_class_radio(_node)
|
53
|
+
"@type='radio'"
|
54
|
+
end
|
55
|
+
|
56
|
+
def visit_pseudo_class_reset(_node)
|
57
|
+
"@type='reset'"
|
58
|
+
end
|
59
|
+
|
60
|
+
def visit_pseudo_class_text(_node)
|
61
|
+
"@type='text'"
|
62
|
+
end
|
63
|
+
|
64
|
+
def visit_pseudo_class_selected(_node)
|
65
|
+
"@selected"
|
66
|
+
end
|
67
|
+
|
68
|
+
def visit_pseudo_class_checked(_node)
|
69
|
+
"@checked"
|
70
|
+
end
|
71
|
+
|
72
|
+
def visit_pseudo_class_odd(_node)
|
73
|
+
"position() mod 2 = 1"
|
74
|
+
end
|
75
|
+
|
76
|
+
def visit_pseudo_class_even(_node)
|
77
|
+
"position() mod 2 = 0"
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
@@ -49,11 +49,11 @@ module Pincers::Extension
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def find_option_by_label(_label, _options)
|
52
|
-
|
52
|
+
search(_options.merge(xpath: ".//option[contains(.,'#{_label}')]")).first
|
53
53
|
end
|
54
54
|
|
55
55
|
def find_option_by_value(_value, _options)
|
56
|
-
|
56
|
+
search(_options.merge(xpath: ".//option[@value='#{_value}']")).first
|
57
57
|
end
|
58
58
|
|
59
59
|
end
|
@@ -5,7 +5,7 @@ module Pincers::Extension
|
|
5
5
|
nk_root = Pincers.for_nokogiri to_html
|
6
6
|
|
7
7
|
unless root?
|
8
|
-
nk_root = nk_root.
|
8
|
+
nk_root = nk_root.search('body > *') # nokogiri will inject valid html structure around contents
|
9
9
|
end
|
10
10
|
|
11
11
|
if _block.nil?
|
@@ -1,21 +1,8 @@
|
|
1
|
-
require 'pincers/support/xpath_builder'
|
2
|
-
|
3
1
|
module Pincers::Extension
|
4
2
|
module Queries
|
5
3
|
|
6
4
|
TEXT_INPUTS = ['text', 'email', 'number', 'email', 'color', 'password', 'search', 'tel', 'url']
|
7
5
|
|
8
|
-
def search(_options={}, &_block)
|
9
|
-
query_options = {
|
10
|
-
limit: _options.delete(:limit)
|
11
|
-
}
|
12
|
-
|
13
|
-
builder = Pincers::Support::XPathBuilder.new _options
|
14
|
-
_block.call builder unless _block.nil?
|
15
|
-
|
16
|
-
xpath builder.expression, query_options
|
17
|
-
end
|
18
|
-
|
19
6
|
def id
|
20
7
|
self[:id]
|
21
8
|
end
|
@@ -40,11 +27,11 @@ module Pincers::Extension
|
|
40
27
|
end
|
41
28
|
|
42
29
|
def selected(_options={})
|
43
|
-
first!.
|
30
|
+
first!.search('option', _options).select(&:selected?)
|
44
31
|
end
|
45
32
|
|
46
33
|
def checked(_options={})
|
47
|
-
first!.
|
34
|
+
first!.search('input', _options).select(&:checked?)
|
48
35
|
end
|
49
36
|
|
50
37
|
def input_mode
|
data/lib/pincers/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pincers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ignacio Baixas
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-09-
|
11
|
+
date: 2015-09-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
@@ -219,8 +219,11 @@ files:
|
|
219
219
|
- lib/pincers/backend/webdriver.rb
|
220
220
|
- lib/pincers/core/cookies.rb
|
221
221
|
- lib/pincers/core/download.rb
|
222
|
+
- lib/pincers/core/query.rb
|
222
223
|
- lib/pincers/core/root_context.rb
|
223
224
|
- lib/pincers/core/search_context.rb
|
225
|
+
- lib/pincers/css/parser.rb
|
226
|
+
- lib/pincers/css/xpath_visitor.rb
|
224
227
|
- lib/pincers/errors.rb
|
225
228
|
- lib/pincers/extension/actions.rb
|
226
229
|
- lib/pincers/extension/labs.rb
|
@@ -233,7 +236,6 @@ files:
|
|
233
236
|
- lib/pincers/support/cookie.rb
|
234
237
|
- lib/pincers/support/cookie_jar.rb
|
235
238
|
- lib/pincers/support/http_client.rb
|
236
|
-
- lib/pincers/support/query.rb
|
237
239
|
- lib/pincers/support/xpath_builder.rb
|
238
240
|
- lib/pincers/version.rb
|
239
241
|
homepage: https://github.com/platanus/pincers
|
@@ -1,31 +0,0 @@
|
|
1
|
-
module Pincers::Support
|
2
|
-
class Query
|
3
|
-
|
4
|
-
attr_reader :lang, :query, :limit
|
5
|
-
|
6
|
-
def initialize(_backend, _lang, _query, _limit)
|
7
|
-
@backend = _backend
|
8
|
-
@lang = _lang
|
9
|
-
@query = _query
|
10
|
-
@limit = _limit
|
11
|
-
end
|
12
|
-
|
13
|
-
def execute(_elements, _force_limit=nil)
|
14
|
-
fun = case @lang
|
15
|
-
when :xpath then :search_by_xpath
|
16
|
-
else :search_by_css end
|
17
|
-
|
18
|
-
explode_elements _elements, fun, _force_limit || @limit
|
19
|
-
end
|
20
|
-
|
21
|
-
def explode_elements(_elements, _fun, _limit)
|
22
|
-
_elements.inject([]) do |r, element|
|
23
|
-
limit = _limit.nil? ? nil : _limit - r.count
|
24
|
-
r = r + @backend.send(_fun, element, @query, limit)
|
25
|
-
break r if !_limit.nil? && r.length >= _limit
|
26
|
-
next r
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
end
|
31
|
-
end
|