knu-mechanize 0.9.3.20090623142847
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/CHANGELOG.rdoc +504 -0
- data/EXAMPLES.rdoc +171 -0
- data/FAQ.rdoc +11 -0
- data/GUIDE.rdoc +122 -0
- data/LICENSE.rdoc +340 -0
- data/Manifest.txt +169 -0
- data/README.rdoc +60 -0
- data/Rakefile +43 -0
- data/examples/flickr_upload.rb +23 -0
- data/examples/mech-dump.rb +7 -0
- data/examples/proxy_req.rb +9 -0
- data/examples/rubyforge.rb +21 -0
- data/examples/spider.rb +11 -0
- data/lib/mechanize.rb +7 -0
- data/lib/www/mechanize.rb +619 -0
- data/lib/www/mechanize/chain.rb +34 -0
- data/lib/www/mechanize/chain/auth_headers.rb +80 -0
- data/lib/www/mechanize/chain/body_decoding_handler.rb +48 -0
- data/lib/www/mechanize/chain/connection_resolver.rb +78 -0
- data/lib/www/mechanize/chain/custom_headers.rb +23 -0
- data/lib/www/mechanize/chain/handler.rb +9 -0
- data/lib/www/mechanize/chain/header_resolver.rb +53 -0
- data/lib/www/mechanize/chain/parameter_resolver.rb +24 -0
- data/lib/www/mechanize/chain/post_connect_hook.rb +0 -0
- data/lib/www/mechanize/chain/pre_connect_hook.rb +22 -0
- data/lib/www/mechanize/chain/request_resolver.rb +32 -0
- data/lib/www/mechanize/chain/response_body_parser.rb +40 -0
- data/lib/www/mechanize/chain/response_header_handler.rb +50 -0
- data/lib/www/mechanize/chain/response_reader.rb +41 -0
- data/lib/www/mechanize/chain/ssl_resolver.rb +42 -0
- data/lib/www/mechanize/chain/uri_resolver.rb +77 -0
- data/lib/www/mechanize/content_type_error.rb +16 -0
- data/lib/www/mechanize/cookie.rb +72 -0
- data/lib/www/mechanize/cookie_jar.rb +191 -0
- data/lib/www/mechanize/file.rb +73 -0
- data/lib/www/mechanize/file_response.rb +62 -0
- data/lib/www/mechanize/file_saver.rb +39 -0
- data/lib/www/mechanize/form.rb +360 -0
- data/lib/www/mechanize/form/button.rb +8 -0
- data/lib/www/mechanize/form/check_box.rb +13 -0
- data/lib/www/mechanize/form/field.rb +28 -0
- data/lib/www/mechanize/form/file_upload.rb +24 -0
- data/lib/www/mechanize/form/image_button.rb +23 -0
- data/lib/www/mechanize/form/multi_select_list.rb +69 -0
- data/lib/www/mechanize/form/option.rb +51 -0
- data/lib/www/mechanize/form/radio_button.rb +38 -0
- data/lib/www/mechanize/form/select_list.rb +45 -0
- data/lib/www/mechanize/headers.rb +12 -0
- data/lib/www/mechanize/history.rb +67 -0
- data/lib/www/mechanize/inspect.rb +90 -0
- data/lib/www/mechanize/monkey_patch.rb +37 -0
- data/lib/www/mechanize/page.rb +181 -0
- data/lib/www/mechanize/page/base.rb +10 -0
- data/lib/www/mechanize/page/frame.rb +22 -0
- data/lib/www/mechanize/page/link.rb +50 -0
- data/lib/www/mechanize/page/meta.rb +51 -0
- data/lib/www/mechanize/pluggable_parsers.rb +103 -0
- data/lib/www/mechanize/redirect_limit_reached_error.rb +18 -0
- data/lib/www/mechanize/redirect_not_get_or_head_error.rb +20 -0
- data/lib/www/mechanize/response_code_error.rb +25 -0
- data/lib/www/mechanize/unsupported_scheme_error.rb +10 -0
- data/lib/www/mechanize/util.rb +76 -0
- data/mechanize.gemspec +41 -0
- data/test/chain/test_argument_validator.rb +14 -0
- data/test/chain/test_auth_headers.rb +25 -0
- data/test/chain/test_custom_headers.rb +18 -0
- data/test/chain/test_header_resolver.rb +28 -0
- data/test/chain/test_parameter_resolver.rb +35 -0
- data/test/chain/test_request_resolver.rb +29 -0
- data/test/chain/test_response_reader.rb +24 -0
- data/test/data/htpasswd +1 -0
- data/test/data/server.crt +16 -0
- data/test/data/server.csr +12 -0
- data/test/data/server.key +15 -0
- data/test/data/server.pem +15 -0
- data/test/helper.rb +129 -0
- data/test/htdocs/alt_text.html +10 -0
- data/test/htdocs/bad_form_test.html +9 -0
- data/test/htdocs/button.jpg +0 -0
- data/test/htdocs/empty_form.html +6 -0
- data/test/htdocs/file_upload.html +26 -0
- data/test/htdocs/find_link.html +41 -0
- data/test/htdocs/form_multi_select.html +16 -0
- data/test/htdocs/form_multival.html +37 -0
- data/test/htdocs/form_no_action.html +18 -0
- data/test/htdocs/form_no_input_name.html +16 -0
- data/test/htdocs/form_select.html +16 -0
- data/test/htdocs/form_select_all.html +16 -0
- data/test/htdocs/form_select_none.html +17 -0
- data/test/htdocs/form_select_noopts.html +10 -0
- data/test/htdocs/form_set_fields.html +14 -0
- data/test/htdocs/form_test.html +188 -0
- data/test/htdocs/frame_test.html +30 -0
- data/test/htdocs/google.html +13 -0
- data/test/htdocs/iframe_test.html +16 -0
- data/test/htdocs/index.html +6 -0
- data/test/htdocs/link with space.html +5 -0
- data/test/htdocs/meta_cookie.html +11 -0
- data/test/htdocs/no_title_test.html +6 -0
- data/test/htdocs/relative/tc_relative_links.html +21 -0
- data/test/htdocs/tc_bad_links.html +5 -0
- data/test/htdocs/tc_base_link.html +8 -0
- data/test/htdocs/tc_blank_form.html +11 -0
- data/test/htdocs/tc_checkboxes.html +19 -0
- data/test/htdocs/tc_encoded_links.html +5 -0
- data/test/htdocs/tc_follow_meta.html +8 -0
- data/test/htdocs/tc_form_action.html +48 -0
- data/test/htdocs/tc_links.html +18 -0
- data/test/htdocs/tc_no_attributes.html +16 -0
- data/test/htdocs/tc_pretty_print.html +17 -0
- data/test/htdocs/tc_radiobuttons.html +17 -0
- data/test/htdocs/tc_referer.html +10 -0
- data/test/htdocs/tc_relative_links.html +19 -0
- data/test/htdocs/tc_textarea.html +23 -0
- data/test/htdocs/unusual______.html +5 -0
- data/test/servlets.rb +365 -0
- data/test/ssl_server.rb +48 -0
- data/test/test_authenticate.rb +71 -0
- data/test/test_bad_links.rb +25 -0
- data/test/test_blank_form.rb +16 -0
- data/test/test_checkboxes.rb +61 -0
- data/test/test_content_type.rb +13 -0
- data/test/test_cookie_class.rb +338 -0
- data/test/test_cookie_jar.rb +362 -0
- data/test/test_cookies.rb +123 -0
- data/test/test_encoded_links.rb +20 -0
- data/test/test_errors.rb +49 -0
- data/test/test_follow_meta.rb +108 -0
- data/test/test_form_action.rb +52 -0
- data/test/test_form_as_hash.rb +61 -0
- data/test/test_form_button.rb +38 -0
- data/test/test_form_no_inputname.rb +15 -0
- data/test/test_forms.rb +564 -0
- data/test/test_frames.rb +25 -0
- data/test/test_get_headers.rb +52 -0
- data/test/test_gzipping.rb +22 -0
- data/test/test_hash_api.rb +45 -0
- data/test/test_history.rb +142 -0
- data/test/test_history_added.rb +16 -0
- data/test/test_html_unscape_forms.rb +39 -0
- data/test/test_if_modified_since.rb +20 -0
- data/test/test_keep_alive.rb +31 -0
- data/test/test_links.rb +120 -0
- data/test/test_mech.rb +268 -0
- data/test/test_mechanize_file.rb +47 -0
- data/test/test_meta.rb +65 -0
- data/test/test_multi_select.rb +106 -0
- data/test/test_no_attributes.rb +13 -0
- data/test/test_option.rb +18 -0
- data/test/test_page.rb +119 -0
- data/test/test_pluggable_parser.rb +145 -0
- data/test/test_post_form.rb +34 -0
- data/test/test_pretty_print.rb +22 -0
- data/test/test_radiobutton.rb +75 -0
- data/test/test_redirect_limit_reached.rb +41 -0
- data/test/test_redirect_verb_handling.rb +45 -0
- data/test/test_referer.rb +39 -0
- data/test/test_relative_links.rb +40 -0
- data/test/test_request.rb +13 -0
- data/test/test_response_code.rb +52 -0
- data/test/test_save_file.rb +48 -0
- data/test/test_scheme.rb +48 -0
- data/test/test_select.rb +106 -0
- data/test/test_select_all.rb +15 -0
- data/test/test_select_none.rb +15 -0
- data/test/test_select_noopts.rb +16 -0
- data/test/test_set_fields.rb +44 -0
- data/test/test_ssl_server.rb +20 -0
- data/test/test_subclass.rb +14 -0
- data/test/test_textarea.rb +45 -0
- data/test/test_upload.rb +109 -0
- data/test/test_verbs.rb +25 -0
- metadata +314 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
module WWW
|
2
|
+
class Mechanize
|
3
|
+
# = Synopsis
|
4
|
+
# This is the default (and base) class for the Pluggable Parsers. If
|
5
|
+
# Mechanize cannot find an appropriate class to use for the content type,
|
6
|
+
# this class will be used. For example, if you download a JPG, Mechanize
|
7
|
+
# will not know how to parse it, so this class will be instantiated.
|
8
|
+
#
|
9
|
+
# This is a good class to use as the base class for building your own
|
10
|
+
# pluggable parsers.
|
11
|
+
#
|
12
|
+
# == Example
|
13
|
+
# require 'rubygems'
|
14
|
+
# require 'mechanize'
|
15
|
+
#
|
16
|
+
# agent = WWW::Mechanize.new
|
17
|
+
# agent.get('http://example.com/foo.jpg').class #=> WWW::Mechanize::File
|
18
|
+
#
|
19
|
+
class File
|
20
|
+
attr_accessor :uri, :response, :body, :code, :filename
|
21
|
+
alias :header :response
|
22
|
+
|
23
|
+
alias :content :body
|
24
|
+
|
25
|
+
def initialize(uri=nil, response=nil, body=nil, code=nil)
|
26
|
+
@uri, @body, @code = uri, body, code
|
27
|
+
@response = Headers.new
|
28
|
+
|
29
|
+
# Copy the headers in to a hash to prevent memory leaks
|
30
|
+
if response
|
31
|
+
response.each { |k,v|
|
32
|
+
@response[k] = v
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
@filename = 'index.html'
|
37
|
+
|
38
|
+
# Set the filename
|
39
|
+
if disposition = @response['content-disposition']
|
40
|
+
disposition.split(/;\s*/).each do |pair|
|
41
|
+
k,v = pair.split(/=/, 2)
|
42
|
+
@filename = v if k && k.downcase == 'filename'
|
43
|
+
end
|
44
|
+
else
|
45
|
+
if @uri
|
46
|
+
@filename = @uri.path.split(/\//).last || 'index.html'
|
47
|
+
@filename << ".html" unless @filename =~ /\./
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
yield self if block_given?
|
52
|
+
end
|
53
|
+
|
54
|
+
# Use this method to save the content of this object to filename
|
55
|
+
def save_as(filename = nil)
|
56
|
+
if filename.nil?
|
57
|
+
filename = @filename
|
58
|
+
number = 1
|
59
|
+
while(::File.exists?(filename))
|
60
|
+
filename = "#{@filename}.#{number}"
|
61
|
+
number += 1
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
::File::open(filename, "wb") { |f|
|
66
|
+
f.write body
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
alias :save :save_as
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module WWW
|
2
|
+
class Mechanize
|
3
|
+
###
|
4
|
+
# Fake response for dealing with file:/// requests
|
5
|
+
class FileResponse
|
6
|
+
def initialize(file_path)
|
7
|
+
@file_path = file_path
|
8
|
+
end
|
9
|
+
|
10
|
+
def read_body
|
11
|
+
if ::File.exists?(@file_path)
|
12
|
+
if directory?
|
13
|
+
yield dir_body
|
14
|
+
else
|
15
|
+
yield ::File.read(@file_path)
|
16
|
+
end
|
17
|
+
else
|
18
|
+
yield ''
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def code
|
23
|
+
::File.exists?(@file_path) ? 200 : 400
|
24
|
+
end
|
25
|
+
|
26
|
+
def content_length
|
27
|
+
return dir_body.length if directory?
|
28
|
+
::File.exists?(@file_path) ? ::File.stat(@file_path).size : 0
|
29
|
+
end
|
30
|
+
|
31
|
+
def each_header; end
|
32
|
+
|
33
|
+
def [](key)
|
34
|
+
return nil unless key.downcase == 'content-type'
|
35
|
+
return 'text/html' if directory?
|
36
|
+
return 'text/html' if ['.html', '.xhtml'].any? { |extn|
|
37
|
+
@file_path =~ /#{extn}$/
|
38
|
+
}
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def each
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_fields(key)
|
46
|
+
[]
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def dir_body
|
51
|
+
'<html><body>' +
|
52
|
+
Dir[::File.join(@file_path, '*')].map { |f|
|
53
|
+
"<a href=\"file://#{f}\">#{::File.basename(f)}</a>"
|
54
|
+
}.join("\n") + '</body></html>'
|
55
|
+
end
|
56
|
+
|
57
|
+
def directory?
|
58
|
+
::File.directory?(@file_path)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module WWW
|
2
|
+
class Mechanize
|
3
|
+
# = Synopsis
|
4
|
+
# This is a pluggable parser that automatically saves every file
|
5
|
+
# it encounters. It saves the files as a tree, reflecting the
|
6
|
+
# host and file path.
|
7
|
+
#
|
8
|
+
# == Example to save all PDF's
|
9
|
+
# require 'rubygems'
|
10
|
+
# require 'mechanize'
|
11
|
+
#
|
12
|
+
# agent = WWW::Mechanize.new
|
13
|
+
# agent.pluggable_parser.pdf = WWW::Mechanize::FileSaver
|
14
|
+
# agent.get('http://example.com/foo.pdf')
|
15
|
+
#
|
16
|
+
class FileSaver < File
|
17
|
+
attr_reader :filename
|
18
|
+
|
19
|
+
def initialize(uri=nil, response=nil, body=nil, code=nil)
|
20
|
+
super(uri, response, body, code)
|
21
|
+
path = uri.path.empty? ? 'index.html' : uri.path.gsub(/^[\/]*/, '')
|
22
|
+
path += 'index.html' if path =~ /\/$/
|
23
|
+
|
24
|
+
split_path = path.split(/\//)
|
25
|
+
filename = split_path.length > 0 ? split_path.pop : 'index.html'
|
26
|
+
joined_path = split_path.join(::File::SEPARATOR)
|
27
|
+
path = if joined_path.empty?
|
28
|
+
uri.host
|
29
|
+
else
|
30
|
+
"#{uri.host}#{::File::SEPARATOR}#{joined_path}"
|
31
|
+
end
|
32
|
+
|
33
|
+
@filename = "#{path}#{::File::SEPARATOR}#{filename}"
|
34
|
+
FileUtils.mkdir_p(path)
|
35
|
+
save_as(@filename)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,360 @@
|
|
1
|
+
require 'www/mechanize/form/field'
|
2
|
+
require 'www/mechanize/form/file_upload'
|
3
|
+
require 'www/mechanize/form/button'
|
4
|
+
require 'www/mechanize/form/image_button'
|
5
|
+
require 'www/mechanize/form/radio_button'
|
6
|
+
require 'www/mechanize/form/check_box'
|
7
|
+
require 'www/mechanize/form/multi_select_list'
|
8
|
+
require 'www/mechanize/form/select_list'
|
9
|
+
require 'www/mechanize/form/option'
|
10
|
+
|
11
|
+
module WWW
|
12
|
+
class Mechanize
|
13
|
+
# =Synopsis
|
14
|
+
# This class encapsulates a form parsed out of an HTML page. Each type
|
15
|
+
# of input fields available in a form can be accessed through this object.
|
16
|
+
# See GlobalForm for more methods.
|
17
|
+
#
|
18
|
+
# ==Example
|
19
|
+
# Find a form and print out its fields
|
20
|
+
# form = page.forms.first # => WWW::Mechanize::Form
|
21
|
+
# form.fields.each { |f| puts f.name }
|
22
|
+
# Set the input field 'name' to "Aaron"
|
23
|
+
# form['name'] = 'Aaron'
|
24
|
+
# puts form['name']
|
25
|
+
class Form
|
26
|
+
attr_accessor :method, :action, :name
|
27
|
+
|
28
|
+
attr_reader :fields, :buttons, :file_uploads, :radiobuttons, :checkboxes
|
29
|
+
attr_accessor :enctype
|
30
|
+
|
31
|
+
alias :elements :fields
|
32
|
+
|
33
|
+
attr_reader :form_node
|
34
|
+
attr_reader :page
|
35
|
+
|
36
|
+
def initialize(node, mech=nil, page=nil)
|
37
|
+
@enctype = node['enctype'] || 'application/x-www-form-urlencoded'
|
38
|
+
@form_node = node
|
39
|
+
@action = Util.html_unescape(node['action'])
|
40
|
+
@method = (node['method'] || 'GET').upcase
|
41
|
+
@name = node['name']
|
42
|
+
@clicked_buttons = []
|
43
|
+
@page = page
|
44
|
+
@mech = mech
|
45
|
+
|
46
|
+
parse
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns whether or not the form contains a field with +field_name+
|
50
|
+
def has_field?(field_name)
|
51
|
+
! fields.find { |f| f.name.eql? field_name }.nil?
|
52
|
+
end
|
53
|
+
|
54
|
+
alias :has_key? :has_field?
|
55
|
+
|
56
|
+
def has_value?(value)
|
57
|
+
! fields.find { |f| f.value.eql? value }.nil?
|
58
|
+
end
|
59
|
+
|
60
|
+
def keys; fields.map { |f| f.name }; end
|
61
|
+
|
62
|
+
def values; fields.map { |f| f.value }; end
|
63
|
+
|
64
|
+
# Add a field with +field_name+ and +value+
|
65
|
+
def add_field!(field_name, value = nil)
|
66
|
+
fields << Field.new(field_name, value)
|
67
|
+
end
|
68
|
+
|
69
|
+
# This method sets multiple fields on the form. It takes a list of field
|
70
|
+
# name, value pairs. If there is more than one field found with the
|
71
|
+
# same name, this method will set the first one found. If you want to
|
72
|
+
# set the value of a duplicate field, use a value which is a Hash with
|
73
|
+
# the key as the index in to the form. The index
|
74
|
+
# is zero based. For example, to set the second field named 'foo', you
|
75
|
+
# could do the following:
|
76
|
+
# form.set_fields( :foo => { 1 => 'bar' } )
|
77
|
+
def set_fields(fields = {})
|
78
|
+
fields.each do |k,v|
|
79
|
+
case v
|
80
|
+
when Hash
|
81
|
+
v.each do |index, value|
|
82
|
+
self.fields_with(:name => k.to_s).[](index).value = value
|
83
|
+
end
|
84
|
+
else
|
85
|
+
value = nil
|
86
|
+
index = 0
|
87
|
+
[v].flatten.each do |val|
|
88
|
+
index = val.to_i unless value.nil?
|
89
|
+
value = val if value.nil?
|
90
|
+
end
|
91
|
+
self.fields_with(:name => k.to_s).[](index).value = value
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Fetch the value of the first input field with the name passed in
|
97
|
+
# ==Example
|
98
|
+
# Fetch the value set in the input field 'name'
|
99
|
+
# puts form['name']
|
100
|
+
def [](field_name)
|
101
|
+
f = field(field_name)
|
102
|
+
f && f.value
|
103
|
+
end
|
104
|
+
|
105
|
+
# Set the value of the first input field with the name passed in
|
106
|
+
# ==Example
|
107
|
+
# Set the value in the input field 'name' to "Aaron"
|
108
|
+
# form['name'] = 'Aaron'
|
109
|
+
def []=(field_name, value)
|
110
|
+
f = field(field_name)
|
111
|
+
if f.nil?
|
112
|
+
add_field!(field_name, value)
|
113
|
+
else
|
114
|
+
f.value = value
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Treat form fields like accessors.
|
119
|
+
def method_missing(id,*args)
|
120
|
+
method = id.to_s.gsub(/=$/, '')
|
121
|
+
if field(method)
|
122
|
+
return field(method).value if args.empty?
|
123
|
+
return field(method).value = args[0]
|
124
|
+
end
|
125
|
+
super
|
126
|
+
end
|
127
|
+
|
128
|
+
# Submit this form with the button passed in
|
129
|
+
def submit button=nil, headers = {}
|
130
|
+
@mech.submit(self, button, headers)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Submit form using +button+. Defaults
|
134
|
+
# to the first button.
|
135
|
+
def click_button(button = buttons.first)
|
136
|
+
submit(button)
|
137
|
+
end
|
138
|
+
|
139
|
+
# This method is sub-method of build_query.
|
140
|
+
# It converts charset of query value of fields into expected one.
|
141
|
+
def proc_query(field)
|
142
|
+
return unless field.query_value
|
143
|
+
field.query_value.map{|(name, val)|
|
144
|
+
[from_native_charset(name), from_native_charset(val.to_s)]
|
145
|
+
}
|
146
|
+
end
|
147
|
+
private :proc_query
|
148
|
+
|
149
|
+
def from_native_charset(str, enc=nil)
|
150
|
+
if page
|
151
|
+
enc ||= page.encoding
|
152
|
+
Util.from_native_charset(str,enc) rescue str
|
153
|
+
else
|
154
|
+
str
|
155
|
+
end
|
156
|
+
end
|
157
|
+
private :from_native_charset
|
158
|
+
|
159
|
+
# This method builds an array of arrays that represent the query
|
160
|
+
# parameters to be used with this form. The return value can then
|
161
|
+
# be used to create a query string for this form.
|
162
|
+
def build_query(buttons = [])
|
163
|
+
query = []
|
164
|
+
|
165
|
+
fields().each do |f|
|
166
|
+
qval = proc_query(f)
|
167
|
+
query.push(*qval)
|
168
|
+
end
|
169
|
+
|
170
|
+
checkboxes().each do |f|
|
171
|
+
if f.checked
|
172
|
+
qval = proc_query(f)
|
173
|
+
query.push(*qval)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
radio_groups = {}
|
178
|
+
radiobuttons().each do |f|
|
179
|
+
fname = from_native_charset(f.name)
|
180
|
+
radio_groups[fname] ||= []
|
181
|
+
radio_groups[fname] << f
|
182
|
+
end
|
183
|
+
|
184
|
+
# take one radio button from each group
|
185
|
+
radio_groups.each_value do |g|
|
186
|
+
checked = g.select {|f| f.checked}
|
187
|
+
|
188
|
+
if checked.size == 1
|
189
|
+
f = checked.first
|
190
|
+
qval = proc_query(f)
|
191
|
+
query.push(*qval)
|
192
|
+
elsif checked.size > 1
|
193
|
+
raise "multiple radiobuttons are checked in the same group!"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
@clicked_buttons.each { |b|
|
198
|
+
qval = proc_query(b)
|
199
|
+
query.push(*qval)
|
200
|
+
}
|
201
|
+
query
|
202
|
+
end
|
203
|
+
|
204
|
+
# This method adds a button to the query. If the form needs to be
|
205
|
+
# submitted with multiple buttons, pass each button to this method.
|
206
|
+
def add_button_to_query(button)
|
207
|
+
@clicked_buttons << button
|
208
|
+
end
|
209
|
+
|
210
|
+
# This method calculates the request data to be sent back to the server
|
211
|
+
# for this form, depending on if this is a regular post, get, or a
|
212
|
+
# multi-part post,
|
213
|
+
def request_data
|
214
|
+
query_params = build_query()
|
215
|
+
case @enctype.downcase
|
216
|
+
when /^multipart\/form-data/
|
217
|
+
boundary = rand_string(20)
|
218
|
+
@enctype = "multipart/form-data; boundary=#{boundary}"
|
219
|
+
params = []
|
220
|
+
query_params.each { |k,v| params << param_to_multipart(k, v) unless k.nil? }
|
221
|
+
@file_uploads.each { |f| params << file_to_multipart(f) }
|
222
|
+
params.collect { |p| "--#{boundary}\r\n#{p}" }.join('') +
|
223
|
+
"--#{boundary}--\r\n"
|
224
|
+
else
|
225
|
+
WWW::Mechanize::Util.build_query_string(query_params)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# Removes all fields with name +field_name+.
|
230
|
+
def delete_field!(field_name)
|
231
|
+
@fields.delete_if{ |f| f.name == field_name}
|
232
|
+
end
|
233
|
+
|
234
|
+
{ :field => :fields,
|
235
|
+
:button => :buttons,
|
236
|
+
:file_upload => :file_uploads,
|
237
|
+
:radiobutton => :radiobuttons,
|
238
|
+
:checkbox => :checkboxes,
|
239
|
+
}.each do |singular,plural|
|
240
|
+
eval(<<-eomethod)
|
241
|
+
def #{plural}_with criteria = {}
|
242
|
+
criteria = {:name => criteria} if String === criteria
|
243
|
+
f = #{plural}.find_all do |thing|
|
244
|
+
criteria.all? { |k,v| v === thing.send(k) }
|
245
|
+
end
|
246
|
+
yield f if block_given?
|
247
|
+
f
|
248
|
+
end
|
249
|
+
|
250
|
+
def #{singular}_with criteria = {}
|
251
|
+
f = #{plural}_with(criteria).first
|
252
|
+
yield f if block_given?
|
253
|
+
f
|
254
|
+
end
|
255
|
+
alias :#{singular} :#{singular}_with
|
256
|
+
eomethod
|
257
|
+
end
|
258
|
+
|
259
|
+
private
|
260
|
+
def parse
|
261
|
+
@fields = []
|
262
|
+
@buttons = []
|
263
|
+
@file_uploads = []
|
264
|
+
@radiobuttons = []
|
265
|
+
@checkboxes = []
|
266
|
+
|
267
|
+
# Find all input tags
|
268
|
+
form_node.search('input').each do |node|
|
269
|
+
type = (node['type'] || 'text').downcase
|
270
|
+
name = node['name']
|
271
|
+
next if name.nil? && !(type == 'submit' || type =='button')
|
272
|
+
case type
|
273
|
+
when 'radio'
|
274
|
+
@radiobuttons << RadioButton.new(node['name'], node['value'], !!node['checked'], self)
|
275
|
+
when 'checkbox'
|
276
|
+
@checkboxes << CheckBox.new(node['name'], node['value'], !!node['checked'], self)
|
277
|
+
when 'file'
|
278
|
+
@file_uploads << FileUpload.new(node['name'], nil)
|
279
|
+
when 'submit'
|
280
|
+
@buttons << Button.new(node['name'], node['value'])
|
281
|
+
when 'button'
|
282
|
+
@buttons << Button.new(node['name'], node['value'])
|
283
|
+
when 'image'
|
284
|
+
@buttons << ImageButton.new(node['name'], node['value'])
|
285
|
+
else
|
286
|
+
@fields << Field.new(node['name'], node['value'] || '')
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
# Find all textarea tags
|
291
|
+
form_node.search('textarea').each do |node|
|
292
|
+
next if node['name'].nil?
|
293
|
+
@fields << Field.new(node['name'], node.inner_text)
|
294
|
+
end
|
295
|
+
|
296
|
+
# Find all select tags
|
297
|
+
form_node.search('select').each do |node|
|
298
|
+
next if node['name'].nil?
|
299
|
+
if node.has_attribute? 'multiple'
|
300
|
+
@fields << MultiSelectList.new(node['name'], node)
|
301
|
+
else
|
302
|
+
@fields << SelectList.new(node['name'], node)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
# Find all submit button tags
|
307
|
+
# FIXME: what can I do with the reset buttons?
|
308
|
+
form_node.search('button').each do |node|
|
309
|
+
type = (node['type'] || 'submit').downcase
|
310
|
+
next if type == 'reset'
|
311
|
+
@buttons << Button.new(node['name'], node['value'])
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def rand_string(len = 10)
|
316
|
+
chars = ("a".."z").to_a + ("A".."Z").to_a
|
317
|
+
string = ""
|
318
|
+
1.upto(len) { |i| string << chars[rand(chars.size-1)] }
|
319
|
+
string
|
320
|
+
end
|
321
|
+
|
322
|
+
def mime_value_quote(str)
|
323
|
+
str.gsub(/(["\r\\])/){|s| '\\' + s}
|
324
|
+
end
|
325
|
+
|
326
|
+
def param_to_multipart(name, value)
|
327
|
+
return "Content-Disposition: form-data; name=\"" +
|
328
|
+
"#{mime_value_quote(name)}\"\r\n" +
|
329
|
+
"\r\n#{value}\r\n"
|
330
|
+
end
|
331
|
+
|
332
|
+
def file_to_multipart(file)
|
333
|
+
file_name = file.file_name ? ::File.basename(file.file_name) : ''
|
334
|
+
body = "Content-Disposition: form-data; name=\"" +
|
335
|
+
"#{mime_value_quote(file.name)}\"; " +
|
336
|
+
"filename=\"#{mime_value_quote(file_name)}\"\r\n" +
|
337
|
+
"Content-Transfer-Encoding: binary\r\n"
|
338
|
+
|
339
|
+
if file.file_data.nil? and ! file.file_name.nil?
|
340
|
+
file.file_data = ::File.open(file.file_name, "rb") { |f| f.read }
|
341
|
+
file.mime_type = WEBrick::HTTPUtils.mime_type(file.file_name,
|
342
|
+
WEBrick::HTTPUtils::DefaultMimeTypes)
|
343
|
+
end
|
344
|
+
|
345
|
+
if file.mime_type != nil
|
346
|
+
body << "Content-Type: #{file.mime_type}\r\n"
|
347
|
+
end
|
348
|
+
|
349
|
+
body <<
|
350
|
+
if file.file_data.respond_to? :read
|
351
|
+
"\r\n#{file.file_data.read}\r\n"
|
352
|
+
else
|
353
|
+
"\r\n#{file.file_data}\r\n"
|
354
|
+
end
|
355
|
+
|
356
|
+
body
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|