pincers 0.5.2 → 0.6.0
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.
- 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
|