class Mechanize::Form
This class encapsulates a form parsed out of an HTML page. Each type of input field available in a form can be accessed through this object.
Examples¶ ↑
Find a form and print out its fields
form = page.forms.first # => Mechanize::Form form.fields.each { |f| puts f.name }
Set the input field 'name' to “Aaron”
form['name'] = 'Aaron' puts form['name']
Constants
- CRLF
Attributes
Character encoding of form data (i.e. UTF-8)
Content-Type for form data (i.e. application/x-www-form-urlencoded)
When true, character encoding errors will never be never raised on form submission. Default is false
Public Class Methods
# File lib/mechanize/form.rb, line 43 def initialize(node, mech = nil, page = nil) @enctype = node['enctype'] || 'application/x-www-form-urlencoded' @node = node @action = Mechanize::Util.html_unescape(node['action']) @method = (node['method'] || 'GET').upcase @name = node['name'] @clicked_buttons = [] @page = page @mech = mech @encoding = node['accept-charset'] || (page && page.encoding) || nil @ignore_encoding_error = false parse end
Public Instance Methods
Fetch the value of the first input field with the name passed in. Example:
puts form['name']
# File lib/mechanize/form.rb, line 241 def [](field_name) f = field(field_name) f && f.value end
Set the value of the first input field with the name passed in. Example:
form['name'] = 'Aaron'
# File lib/mechanize/form.rb, line 248 def []=(field_name, value) f = field(field_name) if f f.value = value else add_field!(field_name, value) end end
Add a field with field_name
and value
# File lib/mechanize/form.rb, line 201 def add_field!(field_name, value = nil) fields << Field.new({'name' => field_name}, value) end
Shorthand for node.at
.
See also Nokogiri::XML::Node#at for details.
# File lib/mechanize/form.rb, line 178
Shorthand for node.at_css
.
See also Nokogiri::XML::Node#at_css for details.
# File lib/mechanize/form.rb, line 185
Shorthand for node.at_xpath
.
See also Nokogiri::XML::Node#at_xpath for details.
# File lib/mechanize/form.rb, line 198 def_delegators :node, :search, :css, :xpath, :at, :at_css, :at_xpath
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.
# File lib/mechanize/form.rb, line 300 def build_query(buttons = []) query = [] @mech.log.info("form encoding: #{encoding}") if @mech && @mech.log save_hash_field_order successful_controls = [] (fields + checkboxes).reject do |f| f.node["disabled"] || f.node["name"] == "" end.sort.each do |f| case f when Mechanize::Form::CheckBox if f.checked successful_controls << f end when Mechanize::Form::Field successful_controls << f end end radio_groups = {} radiobuttons.each do |f| fname = from_native_charset(f.name) radio_groups[fname] ||= [] radio_groups[fname] << f end # take one radio button from each group radio_groups.each_value do |g| checked = g.select(&:checked) if checked.uniq.size > 1 then values = checked.map(&:value).join(', ').inspect name = checked.first.name.inspect raise Mechanize::Error, "radiobuttons #{values} are checked in the #{name} group, " \ "only one is allowed" else successful_controls << checked.first unless checked.empty? end end @clicked_buttons.each { |b| successful_controls << b } successful_controls.sort.each do |ctrl| # DOM order qval = proc_query(ctrl) query.push(*qval) end query end
Find one checkbox that matches criteria
Example:
form.checkbox_with(:name => /woo/).check
# File lib/mechanize/form.rb, line 526
Find all checkboxes that match criteria
Example:
form.checkboxes_with(:name => /woo/).each do |field| field.check end
# File lib/mechanize/form.rb, line 547 elements_with :checkbox, :checkboxes
Shorthand for node.css
.
See also Nokogiri::XML::Node#css for details.
# File lib/mechanize/form.rb, line 164
Removes all fields with name field_name
.
# File lib/mechanize/form.rb, line 425 def delete_field!(field_name) @fields.delete_if{ |f| f.name == field_name} end
This method is a shortcut to get form's DOM class. Common usage:
page.form_with(:dom_class => "foorm")
Note that you can also use :class
to get to this method:
page.form_with(:class => "foorm")
However, attribute values are compared literally as string, so form_with(class: “a”) does not match a form with class=“a b”. Use form_with(css: “form.a”) instead.
# File lib/mechanize/form.rb, line 152 def dom_class @node['class'] end
This method is a shortcut to get form's DOM id. Common usage:
page.form_with(:dom_id => "foorm")
Note that you can also use :id
to get to this method:
page.form_with(:id => "foorm")
# File lib/mechanize/form.rb, line 140 def dom_id @node['id'] end
Same as field_with
but raises an ElementNotFoundError if no field matches criteria
# File lib/mechanize/form.rb, line 437
Find one field that matches criteria
Example:
form.field_with(:id => "exact_field_id").value = 'hello'
# File lib/mechanize/form.rb, line 430
Find all fields that match criteria
Example:
form.fields_with(:value => /foo/).each do |field| field.value = 'hello!' end
# File lib/mechanize/form.rb, line 451 elements_with :field
Find one file upload field that matches criteria
Example:
form.file_upload_with(:file_name => /picture/).value = 'foo'
# File lib/mechanize/form.rb, line 478
Find all file upload fields that match criteria
Example:
form.file_uploads_with(:file_name => /picutre/).each do |field| field.value = 'foo!' end
# File lib/mechanize/form.rb, line 499 elements_with :file_upload
Returns whether or not the form contains a field with field_name
# File lib/mechanize/form.rb, line 59 def has_field?(field_name) fields.any? { |f| f.name == field_name } end
Returns whether or not the form contains a field with value
# File lib/mechanize/form.rb, line 66 def has_value?(value) fields.any? { |f| f.value == value } end
Returns all fields of type Keygen
# File lib/mechanize/form.rb, line 106 def keygens @keygens ||= fields.select { |f| f.class == Keygen } end
Returns all field names (keys) for this form
# File lib/mechanize/form.rb, line 71 def keys fields.map(&:name) end
Treat form fields like accessors.
# File lib/mechanize/form.rb, line 258 def method_missing(meth, *args) (method = meth.to_s).chomp!('=') if field(method) return field(method).value if args.empty? return field(method).value = args[0] end super 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,
# File lib/mechanize/form.rb, line 394 def request_data query_params = build_query() case @enctype.downcase when /^multipart\/form-data/ boundary = rand_string(20) @enctype = "multipart/form-data; boundary=#{boundary}" delimiter = "--#{boundary}\r\n" data = ::String.new query_params.each do |k,v| if k data << delimiter param_to_multipart(k, v, data) end end @file_uploads.each do |f| data << delimiter file_to_multipart(f, data) end data << "--#{boundary}--\r\n" else Mechanize::Util.build_query_string(query_params) end end
This method allows the same form to be submitted second time with the different submit button being clicked.
# File lib/mechanize/form.rb, line 384 def reset # In the future, should add more functionality here to reset the form values to their defaults. @clicked_buttons = [] end
Returns all buttons of type Reset
# File lib/mechanize/form.rb, line 86 def resets @resets ||= buttons.select { |f| f.class == Reset } end
This method adds an index to all fields that have Hash nodes. This enables field sorting to maintain order.
# File lib/mechanize/form.rb, line 357 def save_hash_field_order index = 0 fields.each do |field| if Hash === field.node field.index = index index += 1 end end end
Shorthand for node.search
.
See Nokogiri::XML::Node#search for details.
# File lib/mechanize/form.rb, line 157
This method sets multiple fields on the form. It takes a list of fields
which are name, value pairs.
If there is more than one field found with the same name, this method will set the first one found. If you want to set the value of a duplicate field, use a value which is a Hash with the key as the index in to the form. The index is zero based.
For example, to set the second field named 'foo', you could do the following:
form.set_fields :foo => { 1 => 'bar' }
# File lib/mechanize/form.rb, line 218 def set_fields fields = {} fields.each do |name, v| case v when Hash v.each do |index, value| self.fields_with(:name => name.to_s)[index].value = value end else value = nil index = 0 [v].flatten.each do |val| index = val.to_i if value value = val unless value end self.fields_with(:name => name.to_s)[index].value = value end end end
Submit
the form. Does not include the button
as a form parameter. Use click_button
or provide button as a parameter.
# File lib/mechanize/form.rb, line 271 def submit button = nil, headers = {} @mech.submit(self, button, headers) end
Returns all buttons of type Submit
# File lib/mechanize/form.rb, line 81 def submits @submits ||= buttons.select { |f| f.class == Submit } end
Returns whether or not the form contains a Text
field named field_name
# File lib/mechanize/form.rb, line 121 def text_field?(field_name) texts.find { |f| f.name == field_name } end
Returns whether or not the form contains a Textarea
named field_name
# File lib/mechanize/form.rb, line 131 def textarea_field?(field_name) textareas.find { |f| f.name == field_name } end
Returns all fields of type Textarea
# File lib/mechanize/form.rb, line 101 def textareas @textareas ||= fields.select { |f| f.class == Textarea } end
Returns all fields of type Text
# File lib/mechanize/form.rb, line 91 def texts @texts ||= fields.select { |f| f.class == Text } end
Returns all field values for this form
# File lib/mechanize/form.rb, line 76 def values fields.map(&:value) end
Shorthand for node.xpath
.
See also Nokogiri::XML::Node#xpath for details.
# File lib/mechanize/form.rb, line 171
Private Instance Methods
# File lib/mechanize/form.rb, line 667 def file_to_multipart(file, buf = ::String.new) file_name = file.file_name ? ::File.basename(file.file_name) : '' body = buf << "Content-Disposition: form-data; name=\"".freeze << mime_value_quote(file.name) << "\"; filename=\"".freeze << mime_value_quote(file_name) << "\"\r\nContent-Transfer-Encoding: binary\r\n".freeze if file.file_data.nil? and file.file_name file.file_data = File.binread(file.file_name) file.mime_type = WEBrick::HTTPUtils.mime_type(file.file_name, WEBrick::HTTPUtils::DefaultMimeTypes) end if file.mime_type body << "Content-Type: ".freeze << file.mime_type << CRLF end body << CRLF if file_data = file.file_data if file_data.respond_to? :read body << file_data.read.force_encoding(Encoding::ASCII_8BIT) else body << file_data.b end end body << CRLF end
# File lib/mechanize/form.rb, line 291 def from_native_charset str Mechanize::Util.from_native_charset(str, encoding, @ignore_encoding_error, @mech && @mech.log) end
# File lib/mechanize/form.rb, line 654 def mime_value_quote(str) str.b.gsub(/(["\r\\])/, '\\\\\1') end
# File lib/mechanize/form.rb, line 658 def param_to_multipart(name, value, buf = ::String.new) buf << "Content-Disposition: form-data; name=\"".freeze << mime_value_quote(name) << "\"\r\n\r\n".freeze << value.b << CRLF end
# File lib/mechanize/form.rb, line 579 def parse @fields = [] @buttons = [] @file_uploads = [] @radiobuttons = [] @checkboxes = [] # Find all input tags @node.search('input').each do |node| type = (node['type'] || 'text').downcase name = node['name'] next if name.nil? && !%w[submit button image].include?(type) case type when 'radio' @radiobuttons << RadioButton.new(node, self) when 'checkbox' @checkboxes << CheckBox.new(node, self) when 'file' @file_uploads << FileUpload.new(node, nil) when 'submit' @buttons << Submit.new(node) when 'button' @buttons << Button.new(node) when 'reset' @buttons << Reset.new(node) when 'image' @buttons << ImageButton.new(node) when 'hidden' @fields << Hidden.new(node, node['value'] || '') when 'text' @fields << Text.new(node, node['value'] || '') when 'textarea' @fields << Textarea.new(node, node['value'] || '') else @fields << Field.new(node, node['value'] || '') end end # Find all textarea tags @node.search('textarea').each do |node| next unless node['name'] @fields << Textarea.new(node, node.inner_text) end # Find all select tags @node.search('select').each do |node| next unless node['name'] if node.has_attribute? 'multiple' @fields << MultiSelectList.new(node) else @fields << SelectList.new(node) end end # Find all submit button tags # FIXME: what can I do with the reset buttons? @node.search('button').each do |node| type = (node['type'] || 'submit').downcase next if type == 'reset' @buttons << Button.new(node) end # Find all keygen tags @node.search('keygen').each do |node| @fields << Keygen.new(node, node['value'] || '') end end
This method is sub-method of build_query. It converts charset of query value of fields into expected one.
# File lib/mechanize/form.rb, line 283 def proc_query(field) return unless field.query_value field.query_value.map{|(name, val)| [from_native_charset(name), from_native_charset(val.to_s)] } end
# File lib/mechanize/form.rb, line 647 def rand_string(len = 10) chars = ("a".."z").to_a + ("A".."Z").to_a string = ::String.new 1.upto(len) { |i| string << chars[rand(chars.size-1)] } string end