mechanize 2.0.1 → 2.1.pre.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of mechanize might be problematic. Click here for more details.
- data.tar.gz.sig +0 -0
- data/CHANGELOG.rdoc +82 -0
- data/EXAMPLES.rdoc +1 -1
- data/FAQ.rdoc +9 -9
- data/Manifest.txt +35 -48
- data/README.rdoc +2 -1
- data/Rakefile +16 -3
- data/lib/mechanize.rb +809 -392
- data/lib/mechanize/content_type_error.rb +10 -11
- data/lib/mechanize/cookie.rb +193 -60
- data/lib/mechanize/cookie_jar.rb +39 -86
- data/lib/mechanize/download.rb +59 -0
- data/lib/mechanize/element_matcher.rb +1 -0
- data/lib/mechanize/file.rb +61 -76
- data/lib/mechanize/file_saver.rb +37 -35
- data/lib/mechanize/form.rb +475 -410
- data/lib/mechanize/form/button.rb +4 -7
- data/lib/mechanize/form/check_box.rb +10 -9
- data/lib/mechanize/form/field.rb +52 -42
- data/lib/mechanize/form/file_upload.rb +17 -19
- data/lib/mechanize/form/hidden.rb +3 -0
- data/lib/mechanize/form/image_button.rb +15 -16
- data/lib/mechanize/form/keygen.rb +34 -0
- data/lib/mechanize/form/multi_select_list.rb +20 -9
- data/lib/mechanize/form/option.rb +48 -47
- data/lib/mechanize/form/radio_button.rb +52 -45
- data/lib/mechanize/form/reset.rb +3 -0
- data/lib/mechanize/form/select_list.rb +10 -6
- data/lib/mechanize/form/submit.rb +3 -0
- data/lib/mechanize/form/text.rb +3 -0
- data/lib/mechanize/form/textarea.rb +3 -0
- data/lib/mechanize/headers.rb +17 -19
- data/lib/mechanize/history.rb +60 -61
- data/lib/mechanize/http.rb +5 -0
- data/lib/mechanize/http/agent.rb +485 -218
- data/lib/mechanize/http/auth_challenge.rb +59 -0
- data/lib/mechanize/http/auth_realm.rb +31 -0
- data/lib/mechanize/http/content_disposition_parser.rb +188 -0
- data/lib/mechanize/http/www_authenticate_parser.rb +155 -0
- data/lib/mechanize/monkey_patch.rb +14 -35
- data/lib/mechanize/page.rb +34 -2
- data/lib/mechanize/page/base.rb +6 -7
- data/lib/mechanize/page/frame.rb +5 -5
- data/lib/mechanize/page/image.rb +23 -23
- data/lib/mechanize/page/label.rb +16 -16
- data/lib/mechanize/page/link.rb +16 -0
- data/lib/mechanize/page/meta_refresh.rb +19 -7
- data/lib/mechanize/parser.rb +173 -0
- data/lib/mechanize/pluggable_parsers.rb +126 -83
- data/lib/mechanize/redirect_limit_reached_error.rb +16 -13
- data/lib/mechanize/redirect_not_get_or_head_error.rb +18 -16
- data/lib/mechanize/response_code_error.rb +16 -17
- data/lib/mechanize/robots_disallowed_error.rb +22 -23
- data/lib/mechanize/test_case.rb +659 -0
- data/lib/mechanize/unauthorized_error.rb +3 -0
- data/lib/mechanize/unsupported_scheme_error.rb +4 -6
- data/lib/mechanize/util.rb +0 -12
- data/test/htdocs/form_order_test.html +11 -0
- data/test/htdocs/form_test.html +2 -2
- data/test/htdocs/tc_links.html +1 -0
- data/test/test_mechanize.rb +367 -59
- data/test/test_mechanize_cookie.rb +69 -4
- data/test/test_mechanize_cookie_jar.rb +200 -124
- data/test/test_mechanize_download.rb +43 -0
- data/test/test_mechanize_file.rb +53 -45
- data/test/{test_mechanize_file_response.rb → test_mechanize_file_connection.rb} +2 -2
- data/test/test_mechanize_file_request.rb +2 -2
- data/test/test_mechanize_file_saver.rb +21 -0
- data/test/test_mechanize_form.rb +345 -46
- data/test/test_mechanize_form_check_box.rb +5 -4
- data/test/test_mechanize_form_encoding.rb +10 -16
- data/test/test_mechanize_form_field.rb +45 -3
- data/test/test_mechanize_form_file_upload.rb +20 -0
- data/test/test_mechanize_form_image_button.rb +2 -2
- data/test/test_mechanize_form_keygen.rb +32 -0
- data/test/test_mechanize_form_multi_select_list.rb +84 -0
- data/test/test_mechanize_form_option.rb +55 -0
- data/test/test_mechanize_form_radio_button.rb +78 -0
- data/test/test_mechanize_form_select_list.rb +76 -0
- data/test/test_mechanize_form_textarea.rb +8 -7
- data/test/{test_headers.rb → test_mechanize_headers.rb} +4 -2
- data/test/test_mechanize_history.rb +103 -0
- data/test/test_mechanize_http_agent.rb +525 -17
- data/test/test_mechanize_http_auth_challenge.rb +39 -0
- data/test/test_mechanize_http_auth_realm.rb +49 -0
- data/test/test_mechanize_http_content_disposition_parser.rb +118 -0
- data/test/test_mechanize_http_www_authenticate_parser.rb +146 -0
- data/test/test_mechanize_link.rb +10 -14
- data/test/test_mechanize_page.rb +118 -0
- data/test/test_mechanize_page_encoding.rb +48 -13
- data/test/test_mechanize_page_frame.rb +16 -0
- data/test/test_mechanize_page_link.rb +27 -19
- data/test/test_mechanize_page_meta_refresh.rb +26 -14
- data/test/test_mechanize_parser.rb +289 -0
- data/test/test_mechanize_pluggable_parser.rb +52 -0
- data/test/test_mechanize_redirect_limit_reached_error.rb +24 -0
- data/test/test_mechanize_redirect_not_get_or_head_error.rb +3 -7
- data/test/test_mechanize_subclass.rb +2 -2
- data/test/test_mechanize_util.rb +24 -13
- data/test/test_multi_select.rb +23 -22
- metadata +145 -114
- metadata.gz.sig +0 -0
- data/lib/mechanize/inspect.rb +0 -88
- data/test/helper.rb +0 -175
- data/test/htdocs/form_select_all.html +0 -16
- data/test/htdocs/form_select_none.html +0 -17
- data/test/htdocs/form_select_noopts.html +0 -10
- data/test/htdocs/iframe_test.html +0 -16
- data/test/htdocs/nofollow.html +0 -9
- data/test/htdocs/norobots.html +0 -8
- data/test/htdocs/rel_nofollow.html +0 -8
- data/test/htdocs/tc_base_images.html +0 -10
- data/test/htdocs/tc_images.html +0 -8
- data/test/htdocs/tc_no_attributes.html +0 -16
- data/test/htdocs/tc_radiobuttons.html +0 -17
- data/test/htdocs/test_bad_encoding.html +0 -52
- data/test/servlets.rb +0 -402
- data/test/ssl_server.rb +0 -48
- data/test/test_cookies.rb +0 -129
- data/test/test_form_action.rb +0 -52
- data/test/test_form_as_hash.rb +0 -59
- data/test/test_form_button.rb +0 -46
- data/test/test_frames.rb +0 -34
- data/test/test_history.rb +0 -118
- data/test/test_history_added.rb +0 -16
- data/test/test_html_unscape_forms.rb +0 -46
- data/test/test_if_modified_since.rb +0 -20
- data/test/test_images.rb +0 -19
- data/test/test_no_attributes.rb +0 -13
- data/test/test_option.rb +0 -18
- data/test/test_pluggable_parser.rb +0 -136
- data/test/test_post_form.rb +0 -37
- data/test/test_pretty_print.rb +0 -22
- data/test/test_radiobutton.rb +0 -75
- data/test/test_redirect_limit_reached.rb +0 -39
- data/test/test_referer.rb +0 -81
- data/test/test_relative_links.rb +0 -40
- data/test/test_request.rb +0 -13
- data/test/test_response_code.rb +0 -53
- data/test/test_robots.rb +0 -72
- data/test/test_save_file.rb +0 -48
- data/test/test_scheme.rb +0 -48
- data/test/test_select.rb +0 -119
- data/test/test_select_all.rb +0 -15
- data/test/test_select_none.rb +0 -15
- data/test/test_select_noopts.rb +0 -18
- data/test/test_set_fields.rb +0 -44
- data/test/test_ssl_server.rb +0 -20
@@ -0,0 +1,59 @@
|
|
1
|
+
##
|
2
|
+
# Download is a pluggable parser for downloading files without loading them
|
3
|
+
# into memory first. You may subclass this class to handle content types you
|
4
|
+
# do not wish to load into memory first.
|
5
|
+
#
|
6
|
+
# See Mechanize::PluggableParser for instructions on using this class.
|
7
|
+
|
8
|
+
class Mechanize::Download
|
9
|
+
|
10
|
+
include Mechanize::Parser
|
11
|
+
|
12
|
+
##
|
13
|
+
# Accessor for the IO-like that contains the body
|
14
|
+
|
15
|
+
attr_reader :body_io
|
16
|
+
|
17
|
+
alias content body_io
|
18
|
+
|
19
|
+
##
|
20
|
+
# Creates a new download retrieved from the given +uri+ and +response+
|
21
|
+
# object. The +body_io+ is an IO-like containing the HTTP response body and
|
22
|
+
# +code+ is the HTTP status.
|
23
|
+
|
24
|
+
def initialize uri = nil, response = nil, body_io = nil, code = nil
|
25
|
+
@uri = uri
|
26
|
+
@body_io = body_io
|
27
|
+
@code = code
|
28
|
+
|
29
|
+
@full_path = false unless defined? @full_path
|
30
|
+
|
31
|
+
fill_header response
|
32
|
+
extract_filename
|
33
|
+
|
34
|
+
yield self if block_given?
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Saves a copy of the body_io to +filename+
|
39
|
+
|
40
|
+
def save filename = nil
|
41
|
+
filename = find_free_name filename
|
42
|
+
|
43
|
+
dirname = File.dirname filename
|
44
|
+
FileUtils.mkdir_p dirname
|
45
|
+
|
46
|
+
# Ruby 1.8.7 implements StringIO#path, can't use respond_to? :path
|
47
|
+
if StringIO === @body_io then
|
48
|
+
open filename, 'wb' do |io|
|
49
|
+
until @body_io.eof? do
|
50
|
+
io.write @body_io.read 16384
|
51
|
+
end
|
52
|
+
end
|
53
|
+
else
|
54
|
+
FileUtils.cp @body_io.path, filename
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
data/lib/mechanize/file.rb
CHANGED
@@ -1,80 +1,65 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
# agent.get('http://example.com/foo.jpg').class #=> Mechanize::File
|
17
|
-
#
|
18
|
-
class File
|
19
|
-
extend Forwardable
|
20
|
-
|
21
|
-
attr_accessor :uri, :response, :body, :code, :filename
|
22
|
-
alias :header :response
|
23
|
-
def_delegator :header, :[], :[]
|
24
|
-
def_delegator :header, :[]=, :[]=
|
25
|
-
def_delegator :header, :key?, :key?
|
26
|
-
def_delegator :header, :each, :each
|
27
|
-
def_delegator :header, :canonical_each, :canonical_each
|
28
|
-
|
29
|
-
alias :content :body
|
30
|
-
|
31
|
-
def initialize(uri=nil, response=nil, body=nil, code=nil)
|
32
|
-
@uri = uri
|
33
|
-
@body = body
|
34
|
-
@code = code
|
35
|
-
@response = Headers.new
|
36
|
-
|
37
|
-
# Copy the headers in to a hash to prevent memory leaks
|
38
|
-
if response
|
39
|
-
response.each { |k,v|
|
40
|
-
@response[k] = v
|
41
|
-
}
|
42
|
-
end
|
43
|
-
|
44
|
-
@filename = 'index.html'
|
45
|
-
|
46
|
-
# Set the filename
|
47
|
-
if disposition = @response['content-disposition']
|
48
|
-
disposition.split(/;\s*/).each do |pair|
|
49
|
-
k,v = pair.split(/=/, 2)
|
50
|
-
@filename = v if k && k.downcase == 'filename'
|
51
|
-
end
|
52
|
-
else
|
53
|
-
if @uri
|
54
|
-
@filename = @uri.path.split(/\//).last || 'index.html'
|
55
|
-
@filename << ".html" unless @filename =~ /\./
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
yield self if block_given?
|
60
|
-
end
|
1
|
+
##
|
2
|
+
# This is the base class for the Pluggable Parsers. If Mechanize cannot find
|
3
|
+
# an appropriate class to use for the content type, this class will be used.
|
4
|
+
# For example, if you download an image/jpeg, Mechanize will not know how to
|
5
|
+
# parse it, so this class will be instantiated.
|
6
|
+
#
|
7
|
+
# This is a good class to use as the base class for building your own
|
8
|
+
# pluggable parsers.
|
9
|
+
#
|
10
|
+
# == Example
|
11
|
+
#
|
12
|
+
# require 'mechanize'
|
13
|
+
#
|
14
|
+
# agent = Mechanize.new
|
15
|
+
# agent.get('http://example.com/foo.jpg').class #=> Mechanize::File
|
61
16
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
17
|
+
class Mechanize::File
|
18
|
+
|
19
|
+
include Mechanize::Parser
|
20
|
+
|
21
|
+
##
|
22
|
+
# The HTTP response body, the raw file contents
|
23
|
+
|
24
|
+
attr_accessor :body
|
25
|
+
|
26
|
+
##
|
27
|
+
# The filename for this file based on the content-disposition of the
|
28
|
+
# response or the basename of the URL
|
29
|
+
|
30
|
+
attr_accessor :filename
|
31
|
+
|
32
|
+
alias content body
|
33
|
+
|
34
|
+
##
|
35
|
+
# Creates a new file retrieved from the given +uri+ and +response+ object.
|
36
|
+
# The +body+ is the HTTP response body and +code+ is the HTTP status.
|
37
|
+
|
38
|
+
def initialize uri = nil, response = nil, body = nil, code = nil
|
39
|
+
@uri = uri
|
40
|
+
@body = body
|
41
|
+
@code = code
|
77
42
|
|
78
|
-
|
43
|
+
@full_path = false unless defined? @full_path
|
44
|
+
|
45
|
+
fill_header response
|
46
|
+
extract_filename
|
47
|
+
|
48
|
+
yield self if block_given?
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Use this method to save the content of this object to +filename+
|
53
|
+
|
54
|
+
def save filename = nil
|
55
|
+
filename = find_free_name filename
|
56
|
+
|
57
|
+
open filename, 'wb' do |f|
|
58
|
+
f.write body
|
59
|
+
end
|
79
60
|
end
|
61
|
+
|
62
|
+
alias save_as save
|
63
|
+
|
80
64
|
end
|
65
|
+
|
data/lib/mechanize/file_saver.rb
CHANGED
@@ -1,37 +1,39 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
else
|
29
|
-
"#{uri.host}#{::File::SEPARATOR}#{joined_path}"
|
30
|
-
end
|
31
|
-
|
32
|
-
@filename = "#{path}#{::File::SEPARATOR}#{filename}"
|
33
|
-
FileUtils.mkdir_p(path)
|
34
|
-
save_as(@filename)
|
35
|
-
end
|
1
|
+
##
|
2
|
+
# This is a pluggable parser that automatically saves every file it
|
3
|
+
# encounters. It saves the files as a tree, reflecting the host and file
|
4
|
+
# path.
|
5
|
+
#
|
6
|
+
# == Example
|
7
|
+
#
|
8
|
+
# This example saves all .pdf files
|
9
|
+
#
|
10
|
+
# require 'mechanize'
|
11
|
+
#
|
12
|
+
# agent = Mechanize.new
|
13
|
+
# agent.pluggable_parser.pdf = Mechanize::FileSaver
|
14
|
+
# agent.get('http://example.com/foo.pdf')
|
15
|
+
#
|
16
|
+
# Dir['example.com/*'] # => foo.pdf
|
17
|
+
|
18
|
+
class Mechanize::FileSaver < Mechanize::Download
|
19
|
+
|
20
|
+
attr_reader :filename
|
21
|
+
|
22
|
+
def initialize uri = nil, response = nil, body_io = nil, code = nil
|
23
|
+
@full_path = true
|
24
|
+
|
25
|
+
super
|
26
|
+
|
27
|
+
save @filename
|
36
28
|
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# The save_as alias is provided for backwards compatibility with mechanize
|
32
|
+
# 2.0. It will be removed in mechanize 3.
|
33
|
+
#--
|
34
|
+
# TODO remove in mechanize 3
|
35
|
+
|
36
|
+
alias save_as save
|
37
|
+
|
37
38
|
end
|
39
|
+
|
data/lib/mechanize/form.rb
CHANGED
@@ -1,478 +1,543 @@
|
|
1
1
|
require 'mechanize/element_matcher'
|
2
|
-
require 'mechanize/form/field'
|
3
|
-
require 'mechanize/form/button'
|
4
|
-
require 'mechanize/form/file_upload'
|
5
|
-
require 'mechanize/form/image_button'
|
6
|
-
require 'mechanize/form/multi_select_list'
|
7
|
-
require 'mechanize/form/option'
|
8
|
-
require 'mechanize/form/radio_button'
|
9
|
-
require 'mechanize/form/check_box'
|
10
|
-
require 'mechanize/form/select_list'
|
11
2
|
|
12
|
-
class
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
3
|
+
# This class encapsulates a form parsed out of an HTML page. Each type of
|
4
|
+
# input fields available in a form can be accessed through this object.
|
5
|
+
#
|
6
|
+
# == Examples
|
7
|
+
#
|
8
|
+
# Find a form and print out its fields
|
9
|
+
#
|
10
|
+
# form = page.forms.first # => Mechanize::Form
|
11
|
+
# form.fields.each { |f| puts f.name }
|
12
|
+
#
|
13
|
+
# Set the input field 'name' to "Aaron"
|
14
|
+
#
|
15
|
+
# form['name'] = 'Aaron'
|
16
|
+
# puts form['name']
|
17
|
+
|
18
|
+
class Mechanize::Form
|
19
|
+
|
20
|
+
extend Mechanize::ElementMatcher
|
21
|
+
|
22
|
+
attr_accessor :method, :action, :name
|
23
|
+
|
24
|
+
attr_reader :fields, :buttons, :file_uploads, :radiobuttons, :checkboxes
|
25
|
+
|
26
|
+
# Content-Type for form data (i.e. application/x-www-form-urlencoded)
|
27
|
+
attr_accessor :enctype
|
28
|
+
|
29
|
+
# Character encoding of form data (i.e. UTF-8)
|
30
|
+
attr_accessor :encoding
|
31
|
+
|
32
|
+
# When true, character encoding errors will never be never raised on form
|
33
|
+
# submission. Default is false
|
34
|
+
attr_accessor :ignore_encoding_error
|
35
|
+
|
36
|
+
alias :elements :fields
|
37
|
+
|
38
|
+
attr_reader :form_node
|
39
|
+
attr_reader :page
|
40
|
+
|
41
|
+
def initialize(node, mech=nil, page=nil)
|
42
|
+
@enctype = node['enctype'] || 'application/x-www-form-urlencoded'
|
43
|
+
@form_node = node
|
44
|
+
@action = Mechanize::Util.html_unescape(node['action'])
|
45
|
+
@method = (node['method'] || 'GET').upcase
|
46
|
+
@name = node['name']
|
47
|
+
@clicked_buttons = []
|
48
|
+
@page = page
|
49
|
+
@mech = mech
|
50
|
+
|
51
|
+
@encoding = node['accept-charset'] || (page && page.encoding) || nil
|
52
|
+
@ignore_encoding_error = false
|
53
|
+
parse
|
54
|
+
end
|
28
55
|
|
29
|
-
|
56
|
+
# Returns whether or not the form contains a field with +field_name+
|
57
|
+
def has_field?(field_name)
|
58
|
+
fields.find { |f| f.name == field_name }
|
59
|
+
end
|
30
60
|
|
31
|
-
|
61
|
+
alias :has_key? :has_field?
|
32
62
|
|
33
|
-
|
34
|
-
|
63
|
+
def has_value?(value)
|
64
|
+
fields.find { |f| f.value == value }
|
65
|
+
end
|
35
66
|
|
36
|
-
|
37
|
-
|
67
|
+
def keys; fields.map { |f| f.name }; end
|
68
|
+
|
69
|
+
def values; fields.map { |f| f.value }; end
|
70
|
+
|
71
|
+
def submits ; @submits ||= buttons.select { |f| f.class == Submit }; end
|
72
|
+
def resets ; @resets ||= buttons.select { |f| f.class == Reset }; end
|
73
|
+
def texts ; @texts ||= fields.select { |f| f.class == Text }; end
|
74
|
+
def hiddens ; @hiddens ||= fields.select { |f| f.class == Hidden }; end
|
75
|
+
def textareas; @textareas ||= fields.select { |f| f.class == Textarea }; end
|
76
|
+
def keygens ; @keygens ||= fields.select { |f| f.class == Keygen }; end
|
77
|
+
|
78
|
+
def submit_button?(button_name) submits.find{|f| f.name == button_name}; end
|
79
|
+
def reset_button?(button_name) resets.find{|f| f.name == button_name}; end
|
80
|
+
def text_field?(field_name) texts.find{|f| f.name == field_name}; end
|
81
|
+
def hidden_field?(field_name) hiddens.find{|f| f.name == field_name}; end
|
82
|
+
def textarea_field?(field_name) textareas.find{|f| f.name == field_name}; end
|
83
|
+
|
84
|
+
# This method is a shortcut to get form's DOM id.
|
85
|
+
# Common usage:
|
86
|
+
# page.form_with(:dom_id => "foorm")
|
87
|
+
# Note that you can also use +:id+ to get to this method:
|
88
|
+
# page.form_with(:id => "foorm")
|
89
|
+
def dom_id
|
90
|
+
form_node['id']
|
91
|
+
end
|
38
92
|
|
39
|
-
|
40
|
-
|
41
|
-
|
93
|
+
# This method is a shortcut to get form's DOM class.
|
94
|
+
# Common usage:
|
95
|
+
# page.form_with(:dom_class => "foorm")
|
96
|
+
# Note that you can also use +:class+ to get to this method:
|
97
|
+
# page.form_with(:class => "foorm")
|
98
|
+
def dom_class
|
99
|
+
form_node['class']
|
100
|
+
end
|
42
101
|
|
43
|
-
|
102
|
+
# Add a field with +field_name+ and +value+
|
103
|
+
def add_field!(field_name, value = nil)
|
104
|
+
fields << Field.new({'name' => field_name}, value)
|
105
|
+
end
|
44
106
|
|
45
|
-
|
46
|
-
|
107
|
+
##
|
108
|
+
# This method sets multiple fields on the form. It takes a list of +fields+
|
109
|
+
# which are name, value pairs.
|
110
|
+
#
|
111
|
+
# If there is more than one field found with the same name, this method will
|
112
|
+
# set the first one found. If you want to set the value of a duplicate
|
113
|
+
# field, use a value which is a Hash with the key as the index in to the
|
114
|
+
# form. The index is zero based.
|
115
|
+
#
|
116
|
+
# For example, to set the second field named 'foo', you could do the
|
117
|
+
# following:
|
118
|
+
#
|
119
|
+
# form.set_fields :foo => { 1 => 'bar' }
|
120
|
+
|
121
|
+
def set_fields fields = {}
|
122
|
+
fields.each do |name, v|
|
123
|
+
case v
|
124
|
+
when Hash
|
125
|
+
v.each do |index, value|
|
126
|
+
self.fields_with(:name => name.to_s)[index].value = value
|
127
|
+
end
|
128
|
+
else
|
129
|
+
value = nil
|
130
|
+
index = 0
|
47
131
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
@method = (node['method'] || 'GET').upcase
|
53
|
-
@name = node['name']
|
54
|
-
@clicked_buttons = []
|
55
|
-
@page = page
|
56
|
-
@mech = mech
|
132
|
+
[v].flatten.each do |val|
|
133
|
+
index = val.to_i if value
|
134
|
+
value = val unless value
|
135
|
+
end
|
57
136
|
|
58
|
-
|
59
|
-
|
60
|
-
parse
|
137
|
+
self.fields_with(:name => name.to_s)[index].value = value
|
138
|
+
end
|
61
139
|
end
|
140
|
+
end
|
62
141
|
|
63
|
-
|
64
|
-
|
65
|
-
|
142
|
+
# Fetch the value of the first input field with the name passed in
|
143
|
+
# ==Example
|
144
|
+
# Fetch the value set in the input field 'name'
|
145
|
+
# puts form['name']
|
146
|
+
def [](field_name)
|
147
|
+
f = field(field_name)
|
148
|
+
f && f.value
|
149
|
+
end
|
150
|
+
|
151
|
+
# Set the value of the first input field with the name passed in
|
152
|
+
# ==Example
|
153
|
+
# Set the value in the input field 'name' to "Aaron"
|
154
|
+
# form['name'] = 'Aaron'
|
155
|
+
def []=(field_name, value)
|
156
|
+
f = field(field_name)
|
157
|
+
if f
|
158
|
+
f.value = value
|
159
|
+
else
|
160
|
+
add_field!(field_name, value)
|
66
161
|
end
|
162
|
+
end
|
67
163
|
|
68
|
-
|
164
|
+
# Treat form fields like accessors.
|
165
|
+
def method_missing(meth, *args)
|
166
|
+
method = meth.to_s.gsub(/=$/, '')
|
69
167
|
|
70
|
-
|
71
|
-
|
168
|
+
if field(method)
|
169
|
+
return field(method).value if args.empty?
|
170
|
+
return field(method).value = args[0]
|
72
171
|
end
|
73
172
|
|
74
|
-
|
75
|
-
|
76
|
-
def values; fields.map { |f| f.value }; end
|
77
|
-
|
78
|
-
def submits ; @submits ||= buttons.select { |f| f.class == Submit }; end
|
79
|
-
def resets ; @resets ||= buttons.select { |f| f.class == Reset }; end
|
80
|
-
def texts ; @texts ||= fields.select { |f| f.class == Text }; end
|
81
|
-
def hiddens ; @hiddens ||= fields.select { |f| f.class == Hidden }; end
|
82
|
-
def textareas; @textareas ||= fields.select { |f| f.class == Textarea }; end
|
83
|
-
|
84
|
-
def submit_button?(button_name) submits.find{|f| f.name == button_name}; end
|
85
|
-
def reset_button?(button_name) resets.find{|f| f.name == button_name}; end
|
86
|
-
def text_field?(field_name) texts.find{|f| f.name == field_name}; end
|
87
|
-
def hidden_field?(field_name) hiddens.find{|f| f.name == field_name}; end
|
88
|
-
def textarea_field?(field_name) textareas.find{|f| f.name == field_name}; end
|
89
|
-
|
90
|
-
# This method is a shortcut to get form's DOM id.
|
91
|
-
# Common usage:
|
92
|
-
# page.form_with(:dom_id => "foorm")
|
93
|
-
# Note that you can also use +:id+ to get to this method:
|
94
|
-
# page.form_with(:id => "foorm")
|
95
|
-
def dom_id
|
96
|
-
form_node['id']
|
97
|
-
end
|
173
|
+
super
|
174
|
+
end
|
98
175
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
176
|
+
# Submit this form with the button passed in
|
177
|
+
def submit button=nil, headers = {}
|
178
|
+
@mech.submit(self, button, headers)
|
179
|
+
end
|
180
|
+
|
181
|
+
# Submit form using +button+. Defaults
|
182
|
+
# to the first button.
|
183
|
+
def click_button(button = buttons.first)
|
184
|
+
submit(button)
|
185
|
+
end
|
103
186
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
187
|
+
# This method is sub-method of build_query.
|
188
|
+
# It converts charset of query value of fields into expected one.
|
189
|
+
def proc_query(field)
|
190
|
+
return unless field.query_value
|
191
|
+
field.query_value.map{|(name, val)|
|
192
|
+
[from_native_charset(name), from_native_charset(val.to_s)]
|
193
|
+
}
|
194
|
+
end
|
195
|
+
private :proc_query
|
196
|
+
|
197
|
+
def from_native_charset str
|
198
|
+
Mechanize::Util.from_native_charset(str, encoding, @ignore_encoding_error,
|
199
|
+
@mech && @mech.log)
|
200
|
+
end
|
201
|
+
private :from_native_charset
|
202
|
+
|
203
|
+
# This method builds an array of arrays that represent the query
|
204
|
+
# parameters to be used with this form. The return value can then
|
205
|
+
# be used to create a query string for this form.
|
206
|
+
def build_query(buttons = [])
|
207
|
+
query = []
|
208
|
+
@mech.log.info("form encoding: #{encoding}") if @mech && @mech.log
|
209
|
+
|
210
|
+
successful_controls = []
|
211
|
+
|
212
|
+
(fields + checkboxes).sort.each do |f|
|
213
|
+
case f
|
214
|
+
when Mechanize::Form::CheckBox
|
215
|
+
if f.checked
|
216
|
+
successful_controls << f
|
127
217
|
end
|
218
|
+
when Mechanize::Form::Field
|
219
|
+
successful_controls << f
|
128
220
|
end
|
129
221
|
end
|
130
222
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
f = field(field_name)
|
137
|
-
f && f.value
|
223
|
+
radio_groups = {}
|
224
|
+
radiobuttons.each do |f|
|
225
|
+
fname = from_native_charset(f.name)
|
226
|
+
radio_groups[fname] ||= []
|
227
|
+
radio_groups[fname] << f
|
138
228
|
end
|
139
229
|
|
140
|
-
#
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
230
|
+
# take one radio button from each group
|
231
|
+
radio_groups.each_value do |g|
|
232
|
+
checked = g.select {|f| f.checked}
|
233
|
+
|
234
|
+
if checked.size == 1
|
235
|
+
f = checked.first
|
236
|
+
successful_controls << f
|
237
|
+
elsif checked.size > 1
|
238
|
+
raise Mechanize::Error,
|
239
|
+
"multiple radiobuttons are checked in the same group!"
|
150
240
|
end
|
151
241
|
end
|
152
242
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
if field(method)
|
158
|
-
return field(method).value if args.empty?
|
159
|
-
return field(method).value = args[0]
|
160
|
-
end
|
243
|
+
@clicked_buttons.each { |b|
|
244
|
+
successful_controls << b
|
245
|
+
}
|
161
246
|
|
162
|
-
|
247
|
+
successful_controls.sort.each do |ctrl| # DOM order
|
248
|
+
qval = proc_query(ctrl)
|
249
|
+
query.push(*qval)
|
163
250
|
end
|
164
251
|
|
165
|
-
|
166
|
-
|
167
|
-
@mech.submit(self, button, headers)
|
168
|
-
end
|
252
|
+
query
|
253
|
+
end
|
169
254
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
255
|
+
# This method adds a button to the query. If the form needs to be
|
256
|
+
# submitted with multiple buttons, pass each button to this method.
|
257
|
+
def add_button_to_query(button)
|
258
|
+
@clicked_buttons << button
|
259
|
+
end
|
175
260
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
261
|
+
# This method calculates the request data to be sent back to the server
|
262
|
+
# for this form, depending on if this is a regular post, get, or a
|
263
|
+
# multi-part post,
|
264
|
+
def request_data
|
265
|
+
query_params = build_query()
|
266
|
+
|
267
|
+
case @enctype.downcase
|
268
|
+
when /^multipart\/form-data/
|
269
|
+
boundary = rand_string(20)
|
270
|
+
@enctype = "multipart/form-data; boundary=#{boundary}"
|
271
|
+
|
272
|
+
params = query_params.map do |k,v|
|
273
|
+
param_to_multipart(k, v) if k
|
274
|
+
end.compact
|
275
|
+
|
276
|
+
params.concat @file_uploads.map { |f| file_to_multipart(f) }
|
277
|
+
|
278
|
+
params.map do |part|
|
279
|
+
part.force_encoding('ASCII-8BIT') if part.respond_to? :force_encoding
|
280
|
+
"--#{boundary}\r\n#{part}"
|
281
|
+
end.join('') +
|
282
|
+
"--#{boundary}--\r\n"
|
283
|
+
else
|
284
|
+
Mechanize::Util.build_query_string(query_params)
|
183
285
|
end
|
184
|
-
|
286
|
+
end
|
185
287
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
# This method builds an array of arrays that represent the query
|
192
|
-
# parameters to be used with this form. The return value can then
|
193
|
-
# be used to create a query string for this form.
|
194
|
-
def build_query(buttons = [])
|
195
|
-
query = []
|
196
|
-
@mech.log.info("form encoding: #{encoding}") if @mech && @mech.log
|
197
|
-
|
198
|
-
(fields + checkboxes).sort.each do |f|
|
199
|
-
case f
|
200
|
-
when Form::CheckBox
|
201
|
-
if f.checked
|
202
|
-
qval = proc_query(f)
|
203
|
-
query.push(*qval)
|
204
|
-
end
|
205
|
-
when Form::Field
|
206
|
-
qval = proc_query(f)
|
207
|
-
query.push(*qval)
|
208
|
-
end
|
209
|
-
end
|
288
|
+
# Removes all fields with name +field_name+.
|
289
|
+
def delete_field!(field_name)
|
290
|
+
@fields.delete_if{ |f| f.name == field_name}
|
291
|
+
end
|
210
292
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
293
|
+
##
|
294
|
+
# :method: field_with(criteria)
|
295
|
+
#
|
296
|
+
# Find one field that matches +criteria+
|
297
|
+
# Example:
|
298
|
+
# form.field_with(:id => "exact_field_id").value = 'hello'
|
217
299
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
elsif checked.size > 1
|
227
|
-
raise Mechanize::Error,
|
228
|
-
"multiple radiobuttons are checked in the same group!"
|
229
|
-
end
|
230
|
-
end
|
300
|
+
##
|
301
|
+
# :method: fields_with(criteria)
|
302
|
+
#
|
303
|
+
# Find all fields that match +criteria+
|
304
|
+
# Example:
|
305
|
+
# form.fields_with(:value => /foo/).each do |field|
|
306
|
+
# field.value = 'hello!'
|
307
|
+
# end
|
231
308
|
|
232
|
-
|
233
|
-
qval = proc_query(b)
|
234
|
-
query.push(*qval)
|
235
|
-
}
|
236
|
-
query
|
237
|
-
end
|
309
|
+
elements_with :field
|
238
310
|
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
311
|
+
##
|
312
|
+
# :method: button_with(criteria)
|
313
|
+
#
|
314
|
+
# Find one button that matches +criteria+
|
315
|
+
# Example:
|
316
|
+
# form.button_with(:value => /submit/).value = 'hello'
|
244
317
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
318
|
+
##
|
319
|
+
# :method: buttons_with(criteria)
|
320
|
+
#
|
321
|
+
# Find all buttons that match +criteria+
|
322
|
+
# Example:
|
323
|
+
# form.buttons_with(:value => /submit/).each do |button|
|
324
|
+
# button.value = 'hello!'
|
325
|
+
# end
|
250
326
|
|
251
|
-
|
252
|
-
when /^multipart\/form-data/
|
253
|
-
boundary = rand_string(20)
|
254
|
-
@enctype = "multipart/form-data; boundary=#{boundary}"
|
327
|
+
elements_with :button
|
255
328
|
|
256
|
-
|
257
|
-
|
258
|
-
|
329
|
+
##
|
330
|
+
# :method: file_upload_with(criteria)
|
331
|
+
#
|
332
|
+
# Find one file upload field that matches +criteria+
|
333
|
+
# Example:
|
334
|
+
# form.file_upload_with(:file_name => /picture/).value = 'foo'
|
259
335
|
|
260
|
-
|
336
|
+
##
|
337
|
+
# :method: file_uploads_with(criteria)
|
338
|
+
#
|
339
|
+
# Find all file upload fields that match +criteria+
|
340
|
+
# Example:
|
341
|
+
# form.file_uploads_with(:file_name => /picutre/).each do |field|
|
342
|
+
# field.value = 'foo!'
|
343
|
+
# end
|
261
344
|
|
262
|
-
|
263
|
-
part.force_encoding('ASCII-8BIT') if part.respond_to? :force_encoding
|
264
|
-
"--#{boundary}\r\n#{part}"
|
265
|
-
end.join('') +
|
266
|
-
"--#{boundary}--\r\n"
|
267
|
-
else
|
268
|
-
Mechanize::Util.build_query_string(query_params)
|
269
|
-
end
|
270
|
-
end
|
345
|
+
elements_with :file_upload
|
271
346
|
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
347
|
+
##
|
348
|
+
# :method: radiobutton_with(criteria)
|
349
|
+
#
|
350
|
+
# Find one radio button that matches +criteria+
|
351
|
+
# Example:
|
352
|
+
# form.radiobutton_with(:name => /woo/).check
|
276
353
|
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
# :method: fields_with(criteria)
|
286
|
-
#
|
287
|
-
# Find all fields that match +criteria+
|
288
|
-
# Example:
|
289
|
-
# form.fields_with(:value => /foo/).each do |field|
|
290
|
-
# field.value = 'hello!'
|
291
|
-
# end
|
292
|
-
|
293
|
-
elements_with :field
|
294
|
-
|
295
|
-
##
|
296
|
-
# :method: button_with(criteria)
|
297
|
-
#
|
298
|
-
# Find one button that matches +criteria+
|
299
|
-
# Example:
|
300
|
-
# form.button_with(:value => /submit/).value = 'hello'
|
301
|
-
|
302
|
-
##
|
303
|
-
# :method: buttons_with(criteria)
|
304
|
-
#
|
305
|
-
# Find all buttons that match +criteria+
|
306
|
-
# Example:
|
307
|
-
# form.buttons_with(:value => /submit/).each do |button|
|
308
|
-
# button.value = 'hello!'
|
309
|
-
# end
|
310
|
-
|
311
|
-
elements_with :button
|
312
|
-
|
313
|
-
##
|
314
|
-
# :method: file_upload_with(criteria)
|
315
|
-
#
|
316
|
-
# Find one file upload field that matches +criteria+
|
317
|
-
# Example:
|
318
|
-
# form.file_upload_with(:file_name => /picture/).value = 'foo'
|
319
|
-
|
320
|
-
##
|
321
|
-
# :method: file_uploads_with(criteria)
|
322
|
-
#
|
323
|
-
# Find all file upload fields that match +criteria+
|
324
|
-
# Example:
|
325
|
-
# form.file_uploads_with(:file_name => /picutre/).each do |field|
|
326
|
-
# field.value = 'foo!'
|
327
|
-
# end
|
328
|
-
|
329
|
-
elements_with :file_upload
|
330
|
-
|
331
|
-
##
|
332
|
-
# :method: radiobutton_with(criteria)
|
333
|
-
#
|
334
|
-
# Find one radio button that matches +criteria+
|
335
|
-
# Example:
|
336
|
-
# form.radiobutton_with(:name => /woo/).check
|
337
|
-
|
338
|
-
##
|
339
|
-
# :method: radiobuttons_with(criteria)
|
340
|
-
#
|
341
|
-
# Find all radio buttons that match +criteria+
|
342
|
-
# Example:
|
343
|
-
# form.radiobuttons_with(:name => /woo/).each do |field|
|
344
|
-
# field.check
|
345
|
-
# end
|
346
|
-
|
347
|
-
elements_with :radiobutton
|
348
|
-
|
349
|
-
##
|
350
|
-
# :method: checkbox_with(criteria)
|
351
|
-
#
|
352
|
-
# Find one checkbox that matches +criteria+
|
353
|
-
# Example:
|
354
|
-
# form.checkbox_with(:name => /woo/).check
|
355
|
-
|
356
|
-
##
|
357
|
-
# :method: checkboxes_with(criteria)
|
358
|
-
#
|
359
|
-
# Find all checkboxes that match +criteria+
|
360
|
-
# Example:
|
361
|
-
# form.checkboxes_with(:name => /woo/).each do |field|
|
362
|
-
# field.check
|
363
|
-
# end
|
364
|
-
|
365
|
-
elements_with :checkbox, :checkboxes
|
366
|
-
|
367
|
-
private
|
368
|
-
|
369
|
-
def parse
|
370
|
-
@fields = []
|
371
|
-
@buttons = []
|
372
|
-
@file_uploads = []
|
373
|
-
@radiobuttons = []
|
374
|
-
@checkboxes = []
|
375
|
-
|
376
|
-
# Find all input tags
|
377
|
-
form_node.search('input').each do |node|
|
378
|
-
type = (node['type'] || 'text').downcase
|
379
|
-
name = node['name']
|
380
|
-
next if name.nil? && !%w[submit button image].include?(type)
|
381
|
-
case type
|
382
|
-
when 'radio'
|
383
|
-
@radiobuttons << RadioButton.new(node, self)
|
384
|
-
when 'checkbox'
|
385
|
-
@checkboxes << CheckBox.new(node, self)
|
386
|
-
when 'file'
|
387
|
-
@file_uploads << FileUpload.new(node, nil)
|
388
|
-
when 'submit'
|
389
|
-
@buttons << Submit.new(node)
|
390
|
-
when 'button'
|
391
|
-
@buttons << Button.new(node)
|
392
|
-
when 'reset'
|
393
|
-
@buttons << Reset.new(node)
|
394
|
-
when 'image'
|
395
|
-
@buttons << ImageButton.new(node)
|
396
|
-
when 'hidden'
|
397
|
-
@fields << Hidden.new(node, node['value'] || '')
|
398
|
-
when 'text'
|
399
|
-
@fields << Text.new(node, node['value'] || '')
|
400
|
-
when 'textarea'
|
401
|
-
@fields << Textarea.new(node, node['value'] || '')
|
402
|
-
else
|
403
|
-
@fields << Field.new(node, node['value'] || '')
|
404
|
-
end
|
405
|
-
end
|
354
|
+
##
|
355
|
+
# :method: radiobuttons_with(criteria)
|
356
|
+
#
|
357
|
+
# Find all radio buttons that match +criteria+
|
358
|
+
# Example:
|
359
|
+
# form.radiobuttons_with(:name => /woo/).each do |field|
|
360
|
+
# field.check
|
361
|
+
# end
|
406
362
|
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
363
|
+
elements_with :radiobutton
|
364
|
+
|
365
|
+
##
|
366
|
+
# :method: checkbox_with(criteria)
|
367
|
+
#
|
368
|
+
# Find one checkbox that matches +criteria+
|
369
|
+
# Example:
|
370
|
+
# form.checkbox_with(:name => /woo/).check
|
412
371
|
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
372
|
+
##
|
373
|
+
# :method: checkboxes_with(criteria)
|
374
|
+
#
|
375
|
+
# Find all checkboxes that match +criteria+
|
376
|
+
# Example:
|
377
|
+
# form.checkboxes_with(:name => /woo/).each do |field|
|
378
|
+
# field.check
|
379
|
+
# end
|
380
|
+
|
381
|
+
elements_with :checkbox, :checkboxes
|
382
|
+
|
383
|
+
def pretty_print(q) # :nodoc:
|
384
|
+
q.object_group(self) {
|
385
|
+
q.breakable; q.group(1, '{name', '}') { q.breakable; q.pp name }
|
386
|
+
q.breakable; q.group(1, '{method', '}') { q.breakable; q.pp method }
|
387
|
+
q.breakable; q.group(1, '{action', '}') { q.breakable; q.pp action }
|
388
|
+
q.breakable; q.group(1, '{fields', '}') {
|
389
|
+
fields.each do |field|
|
390
|
+
q.breakable
|
391
|
+
q.pp field
|
420
392
|
end
|
421
|
-
|
393
|
+
}
|
394
|
+
q.breakable; q.group(1, '{radiobuttons', '}') {
|
395
|
+
radiobuttons.each { |b| q.breakable; q.pp b }
|
396
|
+
}
|
397
|
+
q.breakable; q.group(1, '{checkboxes', '}') {
|
398
|
+
checkboxes.each { |b| q.breakable; q.pp b }
|
399
|
+
}
|
400
|
+
q.breakable; q.group(1, '{file_uploads', '}') {
|
401
|
+
file_uploads.each { |b| q.breakable; q.pp b }
|
402
|
+
}
|
403
|
+
q.breakable; q.group(1, '{buttons', '}') {
|
404
|
+
buttons.each { |b| q.breakable; q.pp b }
|
405
|
+
}
|
406
|
+
}
|
407
|
+
end
|
422
408
|
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
409
|
+
alias inspect pretty_inspect # :nodoc:
|
410
|
+
|
411
|
+
private
|
412
|
+
|
413
|
+
def parse
|
414
|
+
@fields = []
|
415
|
+
@buttons = []
|
416
|
+
@file_uploads = []
|
417
|
+
@radiobuttons = []
|
418
|
+
@checkboxes = []
|
419
|
+
|
420
|
+
# Find all input tags
|
421
|
+
form_node.search('input').each do |node|
|
422
|
+
type = (node['type'] || 'text').downcase
|
423
|
+
name = node['name']
|
424
|
+
next if name.nil? && !%w[submit button image].include?(type)
|
425
|
+
case type
|
426
|
+
when 'radio'
|
427
|
+
@radiobuttons << RadioButton.new(node, self)
|
428
|
+
when 'checkbox'
|
429
|
+
@checkboxes << CheckBox.new(node, self)
|
430
|
+
when 'file'
|
431
|
+
@file_uploads << FileUpload.new(node, nil)
|
432
|
+
when 'submit'
|
433
|
+
@buttons << Submit.new(node)
|
434
|
+
when 'button'
|
428
435
|
@buttons << Button.new(node)
|
436
|
+
when 'reset'
|
437
|
+
@buttons << Reset.new(node)
|
438
|
+
when 'image'
|
439
|
+
@buttons << ImageButton.new(node)
|
440
|
+
when 'hidden'
|
441
|
+
@fields << Hidden.new(node, node['value'] || '')
|
442
|
+
when 'text'
|
443
|
+
@fields << Text.new(node, node['value'] || '')
|
444
|
+
when 'textarea'
|
445
|
+
@fields << Textarea.new(node, node['value'] || '')
|
446
|
+
else
|
447
|
+
@fields << Field.new(node, node['value'] || '')
|
429
448
|
end
|
430
449
|
end
|
431
450
|
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
string
|
451
|
+
# Find all textarea tags
|
452
|
+
form_node.search('textarea').each do |node|
|
453
|
+
next unless node['name']
|
454
|
+
@fields << Textarea.new(node, node.inner_text)
|
437
455
|
end
|
438
456
|
|
439
|
-
|
440
|
-
|
457
|
+
# Find all select tags
|
458
|
+
form_node.search('select').each do |node|
|
459
|
+
next unless node['name']
|
460
|
+
if node.has_attribute? 'multiple'
|
461
|
+
@fields << MultiSelectList.new(node)
|
462
|
+
else
|
463
|
+
@fields << SelectList.new(node)
|
464
|
+
end
|
441
465
|
end
|
442
466
|
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
467
|
+
# Find all submit button tags
|
468
|
+
# FIXME: what can I do with the reset buttons?
|
469
|
+
form_node.search('button').each do |node|
|
470
|
+
type = (node['type'] || 'submit').downcase
|
471
|
+
next if type == 'reset'
|
472
|
+
@buttons << Button.new(node)
|
447
473
|
end
|
474
|
+
|
475
|
+
# Find all keygen tags
|
476
|
+
form_node.search('keygen').each do |node|
|
477
|
+
@fields << Keygen.new(node, node['value'] || '')
|
478
|
+
end
|
479
|
+
end
|
448
480
|
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
if file.file_data.nil? and file.file_name
|
457
|
-
file.file_data = open(file.file_name, "rb") { |f| f.read }
|
458
|
-
file.mime_type =
|
459
|
-
WEBrick::HTTPUtils.mime_type(file.file_name,
|
460
|
-
WEBrick::HTTPUtils::DefaultMimeTypes)
|
461
|
-
end
|
481
|
+
def rand_string(len = 10)
|
482
|
+
chars = ("a".."z").to_a + ("A".."Z").to_a
|
483
|
+
string = ""
|
484
|
+
1.upto(len) { |i| string << chars[rand(chars.size-1)] }
|
485
|
+
string
|
486
|
+
end
|
462
487
|
|
463
|
-
|
464
|
-
|
465
|
-
|
488
|
+
def mime_value_quote(str)
|
489
|
+
str.gsub(/(["\r\\])/){|s| '\\' + s}
|
490
|
+
end
|
466
491
|
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
end
|
492
|
+
def param_to_multipart(name, value)
|
493
|
+
return "Content-Disposition: form-data; name=\"" +
|
494
|
+
"#{mime_value_quote(name)}\"\r\n" +
|
495
|
+
"\r\n#{value}\r\n"
|
496
|
+
end
|
473
497
|
|
474
|
-
|
498
|
+
def file_to_multipart(file)
|
499
|
+
file_name = file.file_name ? ::File.basename(file.file_name) : ''
|
500
|
+
body = "Content-Disposition: form-data; name=\"" +
|
501
|
+
"#{mime_value_quote(file.name)}\"; " +
|
502
|
+
"filename=\"#{mime_value_quote(file_name)}\"\r\n" +
|
503
|
+
"Content-Transfer-Encoding: binary\r\n"
|
504
|
+
|
505
|
+
if file.file_data.nil? and file.file_name
|
506
|
+
file.file_data = open(file.file_name, "rb") { |f| f.read }
|
507
|
+
file.mime_type =
|
508
|
+
WEBrick::HTTPUtils.mime_type(file.file_name,
|
509
|
+
WEBrick::HTTPUtils::DefaultMimeTypes)
|
475
510
|
end
|
476
511
|
|
512
|
+
if file.mime_type
|
513
|
+
body << "Content-Type: #{file.mime_type}\r\n"
|
514
|
+
end
|
515
|
+
|
516
|
+
body <<
|
517
|
+
if file.file_data.respond_to? :read
|
518
|
+
"\r\n#{file.file_data.read}\r\n"
|
519
|
+
else
|
520
|
+
"\r\n#{file.file_data}\r\n"
|
521
|
+
end
|
522
|
+
|
523
|
+
body
|
477
524
|
end
|
525
|
+
|
478
526
|
end
|
527
|
+
|
528
|
+
require 'mechanize/form/field'
|
529
|
+
require 'mechanize/form/button'
|
530
|
+
require 'mechanize/form/hidden'
|
531
|
+
require 'mechanize/form/text'
|
532
|
+
require 'mechanize/form/textarea'
|
533
|
+
require 'mechanize/form/submit'
|
534
|
+
require 'mechanize/form/reset'
|
535
|
+
require 'mechanize/form/file_upload'
|
536
|
+
require 'mechanize/form/keygen'
|
537
|
+
require 'mechanize/form/image_button'
|
538
|
+
require 'mechanize/form/multi_select_list'
|
539
|
+
require 'mechanize/form/option'
|
540
|
+
require 'mechanize/form/radio_button'
|
541
|
+
require 'mechanize/form/check_box'
|
542
|
+
require 'mechanize/form/select_list'
|
543
|
+
|