tenderlove-mechanize 0.9.3.20090617085936

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.
Files changed (173) hide show
  1. data/CHANGELOG.rdoc +496 -0
  2. data/EXAMPLES.rdoc +171 -0
  3. data/FAQ.rdoc +11 -0
  4. data/GUIDE.rdoc +122 -0
  5. data/LICENSE.rdoc +340 -0
  6. data/Manifest.txt +169 -0
  7. data/README.rdoc +60 -0
  8. data/Rakefile +43 -0
  9. data/examples/flickr_upload.rb +23 -0
  10. data/examples/mech-dump.rb +7 -0
  11. data/examples/proxy_req.rb +9 -0
  12. data/examples/rubyforge.rb +21 -0
  13. data/examples/spider.rb +11 -0
  14. data/lib/mechanize.rb +7 -0
  15. data/lib/www/mechanize/chain/auth_headers.rb +80 -0
  16. data/lib/www/mechanize/chain/body_decoding_handler.rb +48 -0
  17. data/lib/www/mechanize/chain/connection_resolver.rb +78 -0
  18. data/lib/www/mechanize/chain/custom_headers.rb +23 -0
  19. data/lib/www/mechanize/chain/handler.rb +9 -0
  20. data/lib/www/mechanize/chain/header_resolver.rb +53 -0
  21. data/lib/www/mechanize/chain/parameter_resolver.rb +24 -0
  22. data/lib/www/mechanize/chain/post_connect_hook.rb +0 -0
  23. data/lib/www/mechanize/chain/pre_connect_hook.rb +22 -0
  24. data/lib/www/mechanize/chain/request_resolver.rb +32 -0
  25. data/lib/www/mechanize/chain/response_body_parser.rb +40 -0
  26. data/lib/www/mechanize/chain/response_header_handler.rb +50 -0
  27. data/lib/www/mechanize/chain/response_reader.rb +41 -0
  28. data/lib/www/mechanize/chain/ssl_resolver.rb +42 -0
  29. data/lib/www/mechanize/chain/uri_resolver.rb +77 -0
  30. data/lib/www/mechanize/chain.rb +34 -0
  31. data/lib/www/mechanize/content_type_error.rb +16 -0
  32. data/lib/www/mechanize/cookie.rb +72 -0
  33. data/lib/www/mechanize/cookie_jar.rb +191 -0
  34. data/lib/www/mechanize/file.rb +73 -0
  35. data/lib/www/mechanize/file_response.rb +62 -0
  36. data/lib/www/mechanize/file_saver.rb +39 -0
  37. data/lib/www/mechanize/form/button.rb +8 -0
  38. data/lib/www/mechanize/form/check_box.rb +13 -0
  39. data/lib/www/mechanize/form/field.rb +28 -0
  40. data/lib/www/mechanize/form/file_upload.rb +24 -0
  41. data/lib/www/mechanize/form/image_button.rb +23 -0
  42. data/lib/www/mechanize/form/multi_select_list.rb +69 -0
  43. data/lib/www/mechanize/form/option.rb +51 -0
  44. data/lib/www/mechanize/form/radio_button.rb +38 -0
  45. data/lib/www/mechanize/form/select_list.rb +45 -0
  46. data/lib/www/mechanize/form.rb +360 -0
  47. data/lib/www/mechanize/headers.rb +12 -0
  48. data/lib/www/mechanize/history.rb +67 -0
  49. data/lib/www/mechanize/inspect.rb +90 -0
  50. data/lib/www/mechanize/monkey_patch.rb +37 -0
  51. data/lib/www/mechanize/page/base.rb +10 -0
  52. data/lib/www/mechanize/page/frame.rb +22 -0
  53. data/lib/www/mechanize/page/link.rb +50 -0
  54. data/lib/www/mechanize/page/meta.rb +51 -0
  55. data/lib/www/mechanize/page.rb +176 -0
  56. data/lib/www/mechanize/pluggable_parsers.rb +103 -0
  57. data/lib/www/mechanize/redirect_limit_reached_error.rb +18 -0
  58. data/lib/www/mechanize/redirect_not_get_or_head_error.rb +20 -0
  59. data/lib/www/mechanize/response_code_error.rb +25 -0
  60. data/lib/www/mechanize/unsupported_scheme_error.rb +10 -0
  61. data/lib/www/mechanize/util.rb +76 -0
  62. data/lib/www/mechanize.rb +619 -0
  63. data/mechanize.gemspec +41 -0
  64. data/test/chain/test_argument_validator.rb +14 -0
  65. data/test/chain/test_auth_headers.rb +25 -0
  66. data/test/chain/test_custom_headers.rb +18 -0
  67. data/test/chain/test_header_resolver.rb +28 -0
  68. data/test/chain/test_parameter_resolver.rb +35 -0
  69. data/test/chain/test_request_resolver.rb +29 -0
  70. data/test/chain/test_response_reader.rb +24 -0
  71. data/test/data/htpasswd +1 -0
  72. data/test/data/server.crt +16 -0
  73. data/test/data/server.csr +12 -0
  74. data/test/data/server.key +15 -0
  75. data/test/data/server.pem +15 -0
  76. data/test/helper.rb +129 -0
  77. data/test/htdocs/alt_text.html +10 -0
  78. data/test/htdocs/bad_form_test.html +9 -0
  79. data/test/htdocs/button.jpg +0 -0
  80. data/test/htdocs/empty_form.html +6 -0
  81. data/test/htdocs/file_upload.html +26 -0
  82. data/test/htdocs/find_link.html +41 -0
  83. data/test/htdocs/form_multi_select.html +16 -0
  84. data/test/htdocs/form_multival.html +37 -0
  85. data/test/htdocs/form_no_action.html +18 -0
  86. data/test/htdocs/form_no_input_name.html +16 -0
  87. data/test/htdocs/form_select.html +16 -0
  88. data/test/htdocs/form_select_all.html +16 -0
  89. data/test/htdocs/form_select_none.html +17 -0
  90. data/test/htdocs/form_select_noopts.html +10 -0
  91. data/test/htdocs/form_set_fields.html +14 -0
  92. data/test/htdocs/form_test.html +188 -0
  93. data/test/htdocs/frame_test.html +30 -0
  94. data/test/htdocs/google.html +13 -0
  95. data/test/htdocs/iframe_test.html +16 -0
  96. data/test/htdocs/index.html +6 -0
  97. data/test/htdocs/link with space.html +5 -0
  98. data/test/htdocs/meta_cookie.html +11 -0
  99. data/test/htdocs/no_title_test.html +6 -0
  100. data/test/htdocs/relative/tc_relative_links.html +21 -0
  101. data/test/htdocs/tc_bad_links.html +5 -0
  102. data/test/htdocs/tc_base_link.html +8 -0
  103. data/test/htdocs/tc_blank_form.html +11 -0
  104. data/test/htdocs/tc_checkboxes.html +19 -0
  105. data/test/htdocs/tc_encoded_links.html +5 -0
  106. data/test/htdocs/tc_follow_meta.html +8 -0
  107. data/test/htdocs/tc_form_action.html +48 -0
  108. data/test/htdocs/tc_links.html +18 -0
  109. data/test/htdocs/tc_no_attributes.html +16 -0
  110. data/test/htdocs/tc_pretty_print.html +17 -0
  111. data/test/htdocs/tc_radiobuttons.html +17 -0
  112. data/test/htdocs/tc_referer.html +10 -0
  113. data/test/htdocs/tc_relative_links.html +19 -0
  114. data/test/htdocs/tc_textarea.html +23 -0
  115. data/test/htdocs/unusual______.html +5 -0
  116. data/test/servlets.rb +365 -0
  117. data/test/ssl_server.rb +48 -0
  118. data/test/test_authenticate.rb +71 -0
  119. data/test/test_bad_links.rb +25 -0
  120. data/test/test_blank_form.rb +16 -0
  121. data/test/test_checkboxes.rb +61 -0
  122. data/test/test_content_type.rb +13 -0
  123. data/test/test_cookie_class.rb +338 -0
  124. data/test/test_cookie_jar.rb +362 -0
  125. data/test/test_cookies.rb +123 -0
  126. data/test/test_encoded_links.rb +20 -0
  127. data/test/test_errors.rb +49 -0
  128. data/test/test_follow_meta.rb +108 -0
  129. data/test/test_form_action.rb +44 -0
  130. data/test/test_form_as_hash.rb +61 -0
  131. data/test/test_form_button.rb +38 -0
  132. data/test/test_form_no_inputname.rb +15 -0
  133. data/test/test_forms.rb +564 -0
  134. data/test/test_frames.rb +25 -0
  135. data/test/test_get_headers.rb +52 -0
  136. data/test/test_gzipping.rb +22 -0
  137. data/test/test_hash_api.rb +45 -0
  138. data/test/test_history.rb +142 -0
  139. data/test/test_history_added.rb +16 -0
  140. data/test/test_html_unscape_forms.rb +39 -0
  141. data/test/test_if_modified_since.rb +20 -0
  142. data/test/test_keep_alive.rb +31 -0
  143. data/test/test_links.rb +120 -0
  144. data/test/test_mech.rb +268 -0
  145. data/test/test_mechanize_file.rb +47 -0
  146. data/test/test_meta.rb +65 -0
  147. data/test/test_multi_select.rb +106 -0
  148. data/test/test_no_attributes.rb +13 -0
  149. data/test/test_option.rb +18 -0
  150. data/test/test_page.rb +119 -0
  151. data/test/test_pluggable_parser.rb +145 -0
  152. data/test/test_post_form.rb +34 -0
  153. data/test/test_pretty_print.rb +22 -0
  154. data/test/test_radiobutton.rb +75 -0
  155. data/test/test_redirect_limit_reached.rb +41 -0
  156. data/test/test_redirect_verb_handling.rb +45 -0
  157. data/test/test_referer.rb +39 -0
  158. data/test/test_relative_links.rb +40 -0
  159. data/test/test_request.rb +13 -0
  160. data/test/test_response_code.rb +52 -0
  161. data/test/test_save_file.rb +48 -0
  162. data/test/test_scheme.rb +48 -0
  163. data/test/test_select.rb +106 -0
  164. data/test/test_select_all.rb +15 -0
  165. data/test/test_select_none.rb +15 -0
  166. data/test/test_select_noopts.rb +16 -0
  167. data/test/test_set_fields.rb +44 -0
  168. data/test/test_ssl_server.rb +20 -0
  169. data/test/test_subclass.rb +14 -0
  170. data/test/test_textarea.rb +45 -0
  171. data/test/test_upload.rb +109 -0
  172. data/test/test_verbs.rb +25 -0
  173. metadata +314 -0
@@ -0,0 +1,28 @@
1
+ module WWW
2
+ class Mechanize
3
+ class Form
4
+ # This class represents a field in a form. It handles the following input
5
+ # tags found in a form:
6
+ # text, password, hidden, int, textarea
7
+ #
8
+ # To set the value of a field, just use the value method:
9
+ # field.value = "foo"
10
+ class Field
11
+ attr_accessor :name, :value
12
+
13
+ def initialize(name, value)
14
+ @name = Util.html_unescape(name)
15
+ @value = if value.is_a? String
16
+ Util.html_unescape(value)
17
+ else
18
+ value
19
+ end
20
+ end
21
+
22
+ def query_value
23
+ [[@name, @value || '']]
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ module WWW
2
+ class Mechanize
3
+ class Form
4
+ # This class represents a file upload field found in a form. To use this
5
+ # class, set WWW::FileUpload#file_data= to the data of the file you want
6
+ # to upload and WWW::FileUpload#mime_type= to the appropriate mime type
7
+ # of the file.
8
+ # See the example in EXAMPLES[link://files/EXAMPLES_txt.html]
9
+ class FileUpload < Field
10
+ attr_accessor :file_name # File name
11
+ attr_accessor :mime_type # Mime Type (Optional)
12
+
13
+ alias :file_data :value
14
+ alias :file_data= :value=
15
+
16
+ def initialize(name, file_name)
17
+ @file_name = Util.html_unescape(file_name)
18
+ @file_data = nil
19
+ super(name, @file_data)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ module WWW
2
+ class Mechanize
3
+ class Form
4
+ # This class represents an image button in a form. Use the x and y methods
5
+ # to set the x and y positions for where the mouse "clicked".
6
+ class ImageButton < Button
7
+ attr_accessor :x, :y
8
+
9
+ def initialize(name, value)
10
+ @x = nil
11
+ @y = nil
12
+ super(name, value)
13
+ end
14
+
15
+ def query_value
16
+ super <<
17
+ [@name + ".x", (@x || 0).to_s] <<
18
+ [@name + ".y", (@y || 0).to_s]
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,69 @@
1
+ module WWW
2
+ class Mechanize
3
+ class Form
4
+ # This class represents a select list where multiple values can be selected.
5
+ # MultiSelectList#value= accepts an array, and those values are used as
6
+ # values for the select list. For example, to select multiple values,
7
+ # simply do this:
8
+ # list.value = ['one', 'two']
9
+ # Single values are still supported, so these two are the same:
10
+ # list.value = ['one']
11
+ # list.value = 'one'
12
+ class MultiSelectList < Field
13
+ attr_accessor :options
14
+
15
+ def initialize(name, node)
16
+ value = []
17
+ @options = []
18
+
19
+ # parse
20
+ node.search('option').each do |n|
21
+ option = Option.new(n, self)
22
+ @options << option
23
+ end
24
+ super(name, value)
25
+ end
26
+
27
+ def query_value
28
+ value ? value.collect { |v| [name, v] } : ''
29
+ end
30
+
31
+ # Select no options
32
+ def select_none
33
+ @value = []
34
+ options.each { |o| o.untick }
35
+ end
36
+
37
+ # Select all options
38
+ def select_all
39
+ @value = []
40
+ options.each { |o| o.tick }
41
+ end
42
+
43
+ # Get a list of all selected options
44
+ def selected_options
45
+ @options.find_all { |o| o.selected? }
46
+ end
47
+
48
+ def value=(values)
49
+ select_none
50
+ [values].flatten.each do |value|
51
+ option = options.find { |o| o.value == value }
52
+ if option.nil?
53
+ @value.push(value)
54
+ else
55
+ option.select
56
+ end
57
+ end
58
+ end
59
+
60
+ def value
61
+ value = []
62
+ value.push(*@value)
63
+ value.push(*selected_options.collect { |o| o.value })
64
+ value
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,51 @@
1
+ module WWW
2
+ class Mechanize
3
+ class Form
4
+ # This class contains option an option found within SelectList. A
5
+ # SelectList can have many Option classes associated with it. An option
6
+ # can be selected by calling Option#tick, or Option#click. For example,
7
+ # select the first option in a list:
8
+ # select_list.first.tick
9
+ class Option
10
+ attr_reader :value, :selected, :text, :select_list
11
+
12
+ alias :to_s :value
13
+ alias :selected? :selected
14
+
15
+ def initialize(node, select_list)
16
+ @text = node.inner_text
17
+ @value = Util.html_unescape(node['value'] || node.inner_text)
18
+ @selected = node.has_attribute? 'selected'
19
+ @select_list = select_list # The select list this option belongs to
20
+ end
21
+
22
+ # Select this option
23
+ def select
24
+ unselect_peers
25
+ @selected = true
26
+ end
27
+
28
+ # Unselect this option
29
+ def unselect
30
+ @selected = false
31
+ end
32
+
33
+ alias :tick :select
34
+ alias :untick :unselect
35
+
36
+ # Toggle the selection value of this option
37
+ def click
38
+ unselect_peers
39
+ @selected = !@selected
40
+ end
41
+
42
+ private
43
+ def unselect_peers
44
+ if @select_list.instance_of? SelectList
45
+ @select_list.select_none
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,38 @@
1
+ module WWW
2
+ class Mechanize
3
+ class Form
4
+ # This class represents a radio button found in a Form. To activate the
5
+ # RadioButton in the Form, set the checked method to true.
6
+ class RadioButton < Field
7
+ attr_accessor :checked
8
+
9
+ def initialize(name, value, checked, form)
10
+ @checked = checked
11
+ @form = form
12
+ super(name, value)
13
+ end
14
+
15
+ def check
16
+ uncheck_peers
17
+ @checked = true
18
+ end
19
+
20
+ def uncheck
21
+ @checked = false
22
+ end
23
+
24
+ def click
25
+ checked ? uncheck : check
26
+ end
27
+
28
+ private
29
+ def uncheck_peers
30
+ @form.radiobuttons_with(:name => name).each do |b|
31
+ next if b.value == value
32
+ b.uncheck
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,45 @@
1
+ module WWW
2
+ class Mechanize
3
+ class Form
4
+ # This class represents a select list or drop down box in a Form. Set the
5
+ # value for the list by calling SelectList#value=. SelectList contains a
6
+ # list of Option that were found. After finding the correct option, set
7
+ # the select lists value to the option value:
8
+ # selectlist.value = selectlist.options.first.value
9
+ # Options can also be selected by "clicking" or selecting them. See Option
10
+ class SelectList < MultiSelectList
11
+ def initialize(name, node)
12
+ super(name, node)
13
+ if selected_options.length > 1
14
+ selected_options.reverse[1..selected_options.length].each do |o|
15
+ o.unselect
16
+ end
17
+ end
18
+ end
19
+
20
+ def value
21
+ value = super
22
+ if value.length > 0
23
+ value.last
24
+ elsif @options.length > 0
25
+ @options.first.value
26
+ else
27
+ nil
28
+ end
29
+ end
30
+
31
+ def value=(new)
32
+ if new != new.to_s and new.respond_to? :first
33
+ super([new.first])
34
+ else
35
+ super([new.to_s])
36
+ end
37
+ end
38
+
39
+ def query_value
40
+ value ? [[name, value]] : nil
41
+ end
42
+ end
43
+ end
44
+ end
45
+ 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 excepted 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)
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
@@ -0,0 +1,12 @@
1
+ module WWW
2
+ class Mechanize
3
+ class Headers < Hash
4
+ def [](key)
5
+ super(key.downcase)
6
+ end
7
+ def []=(key, value)
8
+ super(key.downcase, value)
9
+ end
10
+ end
11
+ end
12
+ end