lib/mechanize/form.rb in mechanize-0.4.7 vs lib/mechanize/form.rb in mechanize-0.5.0
- old
+ new
@@ -1,203 +1,228 @@
module WWW
-# Class Form does not work in the case there is some invalid (unbalanced) html
-# involved, such as:
-#
-# <td>
-# <form>
-# </td>
-# <td>
-# <input .../>
-# </form>
-# </td>
-#
-# GlobalForm takes two nodes, the node where the form tag is located
-# (form_node), and another node, from which to start looking for form elements
-# (elements_node) like buttons and the like. For class Form both fall together
-# into one and the same node.
- class GlobalForm
- attr_reader :form_node, :elements_node
- attr_accessor :method, :action, :name
-
- attr_finder :fields, :buttons, :file_uploads, :radiobuttons, :checkboxes
- attr_reader :enctype
-
- def initialize(form_node, elements_node)
- @form_node, @elements_node = form_node, elements_node
-
- @method = (@form_node.attributes['method'] || 'GET').upcase
- @action = @form_node.attributes['action']
- @name = @form_node.attributes['name']
- @enctype = @form_node.attributes['enctype'] || 'application/x-www-form-urlencoded'
- @clicked_buttons = []
-
- parse
- end
-
- # In the case of malformed HTML, fields of multiple forms might occure in this forms'
- # field array. If the fields have the same name, posterior fields overwrite former fields.
- # To avoid this, this method rejects all posterior duplicate fields.
-
- def uniq_fields!
- names_in = {}
- fields.reject! {|f|
- if names_in.include?(f.name)
- true
- else
- names_in[f.name] = true
- false
- end
- }
- end
-
- def build_query(buttons = [])
- query = []
-
- fields().each do |f|
- next unless f.value
- query << [f.name, f.value]
+ class Mechanize
+ # =Synopsis
+ # GlobalForm provides all access to form fields, such as the buttons,
+ # check boxes, and text input.
+ #
+ # GlobalForm takes two nodes, the node where the form tag is located
+ # (form_node), and another node, from which to start looking for form
+ # elements (elements_node) like buttons and the like. For class Form
+ # both fall together into one and the same node.
+ #
+ # Class Form does not work in the case there is some invalid (unbalanced)
+ # html involved, such as:
+ #
+ # <td>
+ # <form>
+ # </td>
+ # <td>
+ # <input .../>
+ # </form>
+ # </td>
+ #
+ class GlobalForm
+ attr_reader :form_node, :elements_node
+ attr_accessor :method, :action, :name
+
+ attr_finder :fields, :buttons, :file_uploads, :radiobuttons, :checkboxes
+ attr_reader :enctype
+
+ def initialize(form_node, elements_node)
+ @form_node, @elements_node = form_node, elements_node
+
+ @method = (@form_node.attributes['method'] || 'GET').upcase
+ @action = @form_node.attributes['action']
+ @name = @form_node.attributes['name']
+ @enctype = @form_node.attributes['enctype'] || 'application/x-www-form-urlencoded'
+ @clicked_buttons = []
+
+ parse
end
-
- checkboxes().each do |f|
- query << [f.name, f.value || "on"] if f.checked
+
+ # In the case of malformed HTML, fields of multiple forms might occure in this forms'
+ # field array. If the fields have the same name, posterior fields overwrite former fields.
+ # To avoid this, this method rejects all posterior duplicate fields.
+
+ def uniq_fields!
+ names_in = {}
+ fields.reject! {|f|
+ if names_in.include?(f.name)
+ true
+ else
+ names_in[f.name] = true
+ false
+ end
+ }
end
-
- radio_groups = {}
- radiobuttons().each do |f|
- radio_groups[f.name] ||= []
- radio_groups[f.name] << f
+
+ # This method builds an array of arrays that represent the query
+ # parameters to be used with this form. The return value can then
+ # be used to create a query string for this form.
+ def build_query(buttons = [])
+ query = []
+
+ fields().each do |f|
+ next unless f.value
+ query << [f.name, f.value]
+ end
+
+ checkboxes().each do |f|
+ query << [f.name, f.value || "on"] if f.checked
+ end
+
+ radio_groups = {}
+ radiobuttons().each do |f|
+ radio_groups[f.name] ||= []
+ radio_groups[f.name] << f
+ end
+
+ # take one radio button from each group
+ radio_groups.each_value do |g|
+ checked = g.select {|f| f.checked}
+
+ if checked.size == 1
+ f = checked.first
+ query << [f.name, f.value || ""]
+ elsif checked.size > 1
+ raise "multiple radiobuttons are checked in the same group!"
+ end
+ end
+
+ @clicked_buttons.each { |b|
+ b.add_to_query(query)
+ }
+
+ query
end
-
- # take one radio button from each group
- radio_groups.each_value do |g|
- checked = g.select {|f| f.checked}
-
- if checked.size == 1
- f = checked.first
- query << [f.name, f.value || ""]
- elsif checked.size > 1
- raise "multiple radiobuttons are checked in the same group!"
+
+ # This method adds a button to the query. If the form needs to be
+ # submitted with multiple buttons, pass each button to this method.
+ def add_button_to_query(button)
+ @clicked_buttons << button
+ end
+
+ # This method calculates the request data to be sent back to the server
+ # for this form, depending on if this is a regular post, get, or a
+ # multi-part post,
+ def request_data
+ query_params = build_query()
+ query = nil
+ case @enctype.downcase
+ when 'multipart/form-data'
+ boundary = rand_string(20)
+ @enctype << ", boundary=#{boundary}"
+ params = []
+ query_params.each { |k,v| params << param_to_multipart(k, v) }
+ @file_uploads.each { |f| params << file_to_multipart(f) }
+ query = params.collect { |p| "--#{boundary}\r\n#{p}" }.join('') +
+ "--#{boundary}--\r\n"
+ else
+ query = WWW::Mechanize.build_query_string(query_params)
end
+
+ query
end
+
+ def inspect
+ "Form: ['#{@name}' #{@method} #{@action}]"
+ end
- @clicked_buttons.each { |b|
- b.add_to_query(query)
- }
-
- query
- end
+ private
+ def parse
+ @fields = WWW::Mechanize::List.new
+ @buttons = WWW::Mechanize::List.new
+ @file_uploads = WWW::Mechanize::List.new
+ @radiobuttons = WWW::Mechanize::List.new
+ @checkboxes = WWW::Mechanize::List.new
+
+ @elements_node.each_recursive {|node|
+ case node.name.downcase
+ when 'input'
+ case (node.attributes['type'] || 'text').downcase
+ when 'text', 'password', 'hidden', 'int'
+ @fields << Field.new(node.attributes['name'], node.attributes['value'] || '')
+ when 'radio'
+ @radiobuttons << RadioButton.new(node.attributes['name'], node.attributes['value'], node.attributes.has_key?('checked'))
+ when 'checkbox'
+ @checkboxes << CheckBox.new(node.attributes['name'], node.attributes['value'], node.attributes.has_key?('checked'))
+ when 'file'
+ @file_uploads << FileUpload.new(node.attributes['name'], node.attributes['value'])
+ when 'submit'
+ @buttons << Button.new(node.attributes['name'], node.attributes['value'])
+ when 'image'
+ @buttons << ImageButton.new(node.attributes['name'], node.attributes['value'])
+ end
+ when 'textarea'
+ @fields << Field.new(node.attributes['name'], node.all_text)
+ when 'select'
+ @fields << SelectList.new(node.attributes['name'], node)
+ end
+ }
+ end
- def add_button_to_query(button)
- @clicked_buttons << button
- end
-
- def request_data
- query_params = build_query()
- query = nil
- case @enctype.downcase
- when 'multipart/form-data'
- boundary = rand_string(20)
- @enctype << ", boundary=#{boundary}"
- params = []
- query_params.each { |k,v| params << param_to_multipart(k, v) }
- @file_uploads.each { |f| params << file_to_multipart(f) }
- query = params.collect { |p| "--#{boundary}\r\n#{p}" }.join('') +
- "--#{boundary}--\r\n"
- else
- query = WWW::Mechanize.build_query_string(query_params)
+ def rand_string(len = 10)
+ chars = ("a".."z").to_a + ("A".."Z").to_a
+ string = ""
+ 1.upto(len) { |i| string << chars[rand(chars.size-1)] }
+ string
end
-
- query
- end
-
- def parse
- @fields = WWW::Mechanize::List.new
- @buttons = WWW::Mechanize::List.new
- @file_uploads = WWW::Mechanize::List.new
- @radiobuttons = WWW::Mechanize::List.new
- @checkboxes = WWW::Mechanize::List.new
-
- @elements_node.each_recursive {|node|
- case node.name.downcase
- when 'input'
- case (node.attributes['type'] || 'text').downcase
- when 'text', 'password', 'hidden', 'int'
- @fields << Field.new(node.attributes['name'], node.attributes['value'] || '')
- when 'radio'
- @radiobuttons << RadioButton.new(node.attributes['name'], node.attributes['value'], node.attributes.has_key?('checked'))
- when 'checkbox'
- @checkboxes << CheckBox.new(node.attributes['name'], node.attributes['value'], node.attributes.has_key?('checked'))
- when 'file'
- @file_uploads << FileUpload.new(node.attributes['name'], node.attributes['value'])
- when 'submit'
- @buttons << Button.new(node.attributes['name'], node.attributes['value'])
- when 'image'
- @buttons << ImageButton.new(node.attributes['name'], node.attributes['value'])
- end
- when 'textarea'
- @fields << Field.new(node.attributes['name'], node.all_text)
- when 'select'
- @fields << SelectList.new(node.attributes['name'], node)
+
+ def mime_value_quote(str)
+ str.gsub(/(["\r\\])/){|s| '\\' + s}
+ end
+
+ def param_to_multipart(name, value)
+ return "Content-Disposition: form-data; name=\"" +
+ "#{mime_value_quote(name)}\"\r\n" +
+ "\r\n#{value}\r\n"
+ end
+
+ def file_to_multipart(file)
+ body = "Content-Disposition: form-data; name=\"" +
+ "#{mime_value_quote(file.name)}\"; " +
+ "filename=\"#{mime_value_quote(file.file_name)}\"\r\n" +
+ "Content-Transfer-Encoding: binary\r\n"
+ if file.mime_type != nil
+ body << "Content-Type: #{file.mime_type}\r\n"
end
- }
+
+ body << "\r\n#{file.file_data}\r\n"
+
+ body
+ end
end
+
+ # =Synopsis
+ # This class encapsulates a form parsed out of an HTML page. Each type
+ # of input fields available in a form can be accessed through this object.
+ # See GlobalForm for more methods.
+ #
+ # ==Example
+ # Find a form and print out its fields
+ # form = page.forms.first # => WWW::Mechanize::Form
+ # form.fields.each { |f| puts f.name }
+ class Form < GlobalForm
+ attr_reader :node
+
+ def initialize(node)
+ @node = node
+ super(@node, @node)
+ end
- def inspect
- string = "Form: ['#{@name}' -> #{@action}]\n"
- string << "[radiobuttons]\n"
- @radiobuttons.each { |f| string << f.inspect }
- string << "[checkboxes]\n"
- @checkboxes.each { |f| string << f.inspect }
- string << "[fields]\n"
- @fields.each { |f| string << f.inspect }
- string << "[buttons]\n"
- @buttons.each { |f| string << f.inspect }
- string
- end
-
- private
- def rand_string(len = 10)
- chars = ("a".."z").to_a + ("A".."Z").to_a
- string = ""
- 1.upto(len) { |i| string << chars[rand(chars.size-1)] }
- string
- end
-
- def mime_value_quote(str)
- str.gsub(/(["\r\\])/){|s| '\\' + s}
- end
-
- def param_to_multipart(name, value)
- return "Content-Disposition: form-data; name=\"" +
- "#{mime_value_quote(name)}\"\r\n" +
- "\r\n#{value}\r\n"
- end
-
- def file_to_multipart(file)
- body = "Content-Disposition: form-data; name=\"" +
- "#{mime_value_quote(file.name)}\"; " +
- "filename=\"#{mime_value_quote(file.file_name)}\"\r\n" +
- "Content-Transfer-Encoding: binary\r\n"
- if file.mime_type != nil
- body << "Content-Type: #{file.mime_type}\r\n"
+ # Fetch the first field whose name is equal to field_name
+ def field(field_name)
+ fields.find { |f| f.name.eql? field_name }
end
-
- body << "\r\n#{file.file_data}\r\n"
-
- body
- end
- end
-
- class Form < GlobalForm
- attr_reader :node
-
- def initialize(node)
- @node = node
- super(@node, @node)
- end
- # Fetch the first field whose name is equal to field_name
- def field(field_name)
- fields.find { |f| f.name.eql? field_name }
+ # Treat form fields like accessors.
+ def method_missing(id,*args)
+ method = id.to_s.gsub(/=$/, '')
+ if field(method)
+ return field(method).value if args.empty?
+ return field(method).value = args[0]
+ end
+ super
+ end
end
end
end