HTML::DOM::Element::Form - A Perl class for representing 'form' elements in an HTML DOM tree


HTML-DOM documentation Contained in the HTML-DOM distribution.

Index


Code Index:

NAME

Top

HTML::DOM::Element::Form - A Perl class for representing 'form' elements in an HTML DOM tree

SYNOPSIS

Top

  use HTML::DOM;
  $doc = HTML::DOM->new;
  $elem = $doc->createElement('form');

  $elem->method('GET') # set attribute
  $elem->method;       # get attribute
  $elem->enctype;
  $elem->tagName;
  # etc

DESCRIPTION

Top

This class implements 'form' elements in an HTML::DOM tree. It implements the HTMLFormElement DOM interface and inherits from HTML::DOM::Element (q.v.).

A form object can be used as a hash or an array, to access its input fields, so $form->[0] and $form->{name} are shorthand for $form->elements->[0] and $form->elements->{name}, respectively.

This class also tries to mimic HTML::Form, but is not entirely compatible with its interface. See WWW::Mechanize COMPATIBILITY, below.

DOM METHODS

Top

In addition to those inherited from HTML::DOM::Element and HTML::DOM::Node, this class implements the following DOM methods:

elements

Returns a collection (HTML::DOM::Collection::Elements object) in scalar context, or a list in list context, of all the input fields this form contains. This differs slightly from the inputs method (part of the HTML::Form interface) in that it includes 'button' elements, whereas inputs does not (though it does include 'input' elements with 'button' for the type).

length

Same as $form->elements->length.

name
acceptCharset
action
enctype
method
target

Each of these returns the corresponding HTML attribute (acceptCharset corresponds to the 'accept-charset' attribute). If you pass an argument, it will become the new value of the attribute, and the old value will be returned.

submit

This triggers the form's 'submit' event, calling the default event handler (see EVENT HANDLING in HTML::DOM). It is up to the default event handler to take any further action. The form's make_request method may come in handy.

This method is actually just short for $form->trigger_event('submit'). (See HTML::DOM::EventTarget.)

reset

This triggers the form's 'reset' event.

trigger_event

This class overrides the superclasses' method to trigger the default event handler for form submissions, when the submit event occurs, and reset the form when a reset event occurs.

WWW::Mechanize COMPATIBILITY

Top

In order to work with WWW::Mechanize, this module mimics, and is partly compatible with the interface of, HTML::Form.

HTML::Form's class methods do not apply. If you call HTML::DOM::Element::Form->parse, for instance, you'll just get an error, because it doesn't exist.

The dump and try_others methods do not exist either.

The click method behaves differently from HTML::Form's, in that it does not call make_request, but triggers a 'click' event if there is a button to click, or a 'submit' event otherwise.

The method, action, enctype, attr, inputs, find_input, value, param, make_request and form methods should work as expected.

SEE ALSO

Top

HTML::DOM

HTML::DOM::Element

HTML::DOM::Collection::Elements

HTML::Form


HTML-DOM documentation Contained in the HTML-DOM distribution.

package HTML::DOM::Element::Form;

use strict;
use warnings;

no Carp();
use URI;

require HTML::DOM::Element;
require HTML::DOM::NodeList::Magic;
#require HTML::DOM::Collection::Elements;

our $VERSION = '0.048';
our @ISA = qw'HTML::DOM::Element';

use overload fallback => 1,
'@{}' => sub { shift->elements },
'%{}' => sub {
	my $self = shift;
	$self->isa(scalar caller) || caller->isa('HTML::TreeBuilder')
		and return $self;
	$self->elements;
};

my %elem_elems = (
	input    => 1,
	button   => 1,
	select   => 1,
	textarea => 1,
);
sub elements {
	my $self = shift;
	my $collection = $self->{_HTML_DOM_elems} ||= do {
		my $collection = HTML::DOM::Collection::Elements->new(
		my $list = HTML::DOM::NodeList::Magic->new(
		   sub {
		     no warnings 'uninitialized';
		     grep(
		      $elem_elems{tag $_} && attr $_ 'type', ne 'image',
		      $self->descendants
		     ),
		     @{ $self->{_HTML_DOM_mg_elems}||[] }
		    } 
		));
		$self->ownerDocument-> _register_magic_node_list($list);
		$collection;
	};
	weaken $self;
	if (wantarray) {
		@$collection
	}
	else {
		$collection;
	}
}
sub add_element { # helper routine that formies use to add themselves to
 my $self = shift;  # the elements list
 push @{ $self->{_HTML_DOM_mg_elems} ||= [] }, shift
  if $elem_elems{ $_[0]->tag };
 return;
}
sub remove_element { # and this is how formies remove themselves when they
 my $self = shift;   # get moved around the DOM
 my $removee = shift;
 @{ $self->{_HTML_DOM_mg_elems} }
  = grep $_ != $removee, @{ $self->{_HTML_DOM_elems} ||= [] }
}

sub length        { shift->elements              -> length }
sub name          { no warnings; shift->_attr( name          => @_) . ''  }
sub acceptCharset { shift->_attr('accept-charset' => @_)    }
sub action        {
	my $self = shift;
	(my $base = $self->ownerDocument->base)
		or return $self->_attr('action', @_);
	(new_abs URI
		$self->_attr('action' => @_),
		$self->ownerDocument->base)
	 ->as_string
}
sub enctype       {
	my $ret = shift->_attr('enctype'        => @_);
	defined $ret ? $ret : 'application/x-www-form-urlencoded'
}
*encoding=*enctype;
sub method        {
	my $ret = shift->_attr('method'         => @_);
	defined $ret ? lc $ret : 'get'
}
sub target        { shift->_attr('target'         => @_)    }

sub submit { shift->trigger_event('submit') }

sub reset {
	shift->trigger_event('reset');
}

sub trigger_event {
	my ($a,$evnt) = (shift,shift);
	$a->SUPER::trigger_event(
		$evnt,
		submit_default =>
			$a->ownerDocument->
				default_event_handler_for('submit'),
		reset_default => sub {
			$_->_reset for shift->target->elements
		},
		@_,
	);
}

# ------ HTML::Form compatibility methods ------ #

sub inputs {
	my @ret;
	my %pos;
	my $self = shift;
	# This used to use ‘$self->elements’, but ->elements no longer
	# includes image buttons.
	for(
	 grep($elem_elems{tag $_}, $self->descendants),
	 @{ $self->{_HTML_DOM_mg_elems}||[] }
	) {
	  next if (my $tag = tag $_) eq 'button'; # HTML::Form doesn't deal
	                                          # with <button>s.
	  no warnings 'uninitialized'; # for 5.11.0
	  if(lc $_->attr('type') eq 'radio') {
	    my $name = name $_;
	    exists $pos{$name} ? push @{$ret[$pos{$name}]}, $_
	      :( push(@ret, [$_]),
	          $pos{$name} = $#ret );
	    next
	  }
	  push @ret, $tag eq 'select'
	    ? $_->attr('multiple')
	      ? $_->find('option')
	      : scalar $_->options
	    : $_
	}
	map ref $_ eq 'ARRAY' ? new HTML::DOM::NodeList::Radio $_ : $_,
		@ret
}

sub click  # 22/Sep/7: stolen from HTML::Form and modified (particularly
{          # the last line) so I don't have to mess with Hook::LexWrap
    my $self = shift;
    my $name;
    $name = shift if (@_ % 2) == 1;  # odd number of arguments

    # try to find first submit button to activate
    for ($self->inputs) {
        next unless $_->type =~ /^(?:submit|image)\z/;
        next if $name && $_->name ne $name;
	next if $_->disabled;
	$_->click($self, @_);return
    }
    Carp::croak("No clickable input with name $name") if $name;
    $self->trigger_event('submit');
}

# These three were shamelessly stolen from HTML::Form:
sub value
{
    package
     HTML::Form;
    my $self = shift;
    my $key  = shift;
    my $input = $self->find_input($key);
    Carp::croak("No such field '$key'") unless $input;
    local $Carp::CarpLevel = 1;
    $input->value(@_);
}


sub find_input
{
    package
	HTML::Form; # so caller tricks work
    my($self, $name, $type, $no) = @_;
    if (wantarray) {
	my @res;
	my $c;
	for ($self->inputs) {
	    if (defined $name) {
		next unless defined(my $n = $_->name);
		next if $name ne $n;
	    }
	    next if $type && $type ne $_->type;
	    $c++;
	    next if $no && $no != $c;
	    push(@res, $_);
	}
	return @res;
	
    }
    else {
	$no ||= 1;
	for ($self->inputs) {
	    if (defined $name) {
		next unless defined(my $n = $_->name);
		next if $name ne $n;
	    }
	    next if $type && $type ne $_->type;
	    next if --$no;
	    return $_;
	}
	return undef;
    }
}

sub param {
	package
		HTML::Form;
    my $self = shift;
    if (@_) {
        my $name = shift;
        my @inputs;
        for ($self->inputs) {
            my $n = $_->name;
            next if !defined($n) || $n ne $name;
            push(@inputs, $_);
        }

        if (@_) {
            # set
            die "No '$name' parameter exists" unless @inputs;
	    my @v = @_;
	    @v = @{$v[0]} if @v == 1 && ref($v[0]);
            while (@v) {
                my $v = shift @v;
                my $err;
                for my $i (0 .. @inputs-1) {
                    eval {
                        $inputs[$i]->value($v);
                    };
                    unless ($@) {
                        undef($err);
                        splice(@inputs, $i, 1);
                        last;
                    }
                    $err ||= $@;
                }
                die $err if $err;
            }

	    # the rest of the input should be cleared
	    for (@inputs) {
		$_->value(undef);
	    }
        }
        else {
            # get
            my @v;
            for (@inputs) {
		if (defined(my $v = $_->value)) {
		    push(@v, $v);
		}
            }
            return wantarray ? @v : $v[0];
        }
    }
    else {
        # list parameter names
        my @n;
        my %seen;
        for ($self->inputs) {
            my $n = $_->name;
            next if !defined($n) || $seen{$n}++;
            push(@n, $n);
        }
        return @n;
    }
}


my $ascii_encodings_re;
my $encodings_re;

sub _encoding_ok {
	my ($enc,$xwfu) =@ _;
	$enc =~ s/^(?:x-?)?mac-?/mac/i;
	($enc) x (Encode'resolve_alias($enc)||return)
		=~ ($xwfu ? $ascii_encodings_re : $encodings_re ||=qr/^${\
						join'|',map quotemeta,
								encodings Encode 'Byte'
				}\z/);
}

sub _apply_charset {
	my($charsets,$apply_to) = @_; # array refs
	my ($charset,@ret);
	for(@$charsets) {
#use DDS; Dump $_ if @$apply_to == 1;
		eval {
			@ret = ();
			# Can’t use map here, because it could die. (In
			# perl5.8.x, dying inside a map is a very
			# bad idea.)
			for my $applyee(@$apply_to) {
				push @ret, ref $applyee
					? $applyee
					: Encode::encode(
						$_,$applyee,9
					); # 1=croak, 8=leave src alone
			}                                         
			# Phew, we survived!
			$charset = $_;
		} && last;
	}
	unless($charset) {
		# If none of the charsets applied, we just use the first
		# one in the list (or fall back to utf8, since that’s the
		# sensible thing to do these days), replacing unknown
		# chars with ?
		my $fallback;
		$charset = $$charsets[0]||(++$fallback,'utf8');
		@ret = map ref$_ ? $_ : Encode'encode($charset,$_),
			@$apply_to;
		$fallback and $charset = 'utf-8';
	}
	$charset,\@ret;
}

# ~~~ This does not take non-ASCII file names into account, but I can’t
#     really do that yet, since perl itself doesn’t support those properly
#     yet, either.
# This one was stolen from HTML::Form but then modified extensively.
sub make_request
{
    my $self = shift;
    my $method  = $self->method;
    my $uri     = $self->action;
    my $xwfu = $method eq 'get'
        || $self->enctype !~ /^multipart\/form-data\z/i;
    my @form    = $self->form;

    # Get the charset and encode the form fields, if necessary. The HTML
    # spec says that the a/x-w-f-u MIME type only accepts ASCII, but we’ll
    # be more forgiving, for the sake of realism.  But to be compliant with
    # the spec in cases where it can apply  (e.g.,  a UTF-16 page with just
    # ASCII in its form data),  we only accept ASCII-based  encodings  for
    # this enctype.
    my @charsets;
    { push @charsets, split ' ', $self->acceptCharset||next}
    require Encode;
    @charsets = map _encoding_ok($_, $xwfu),
                @charsets;
    unless(@charsets){{
        # We only revert to the doc charset when accept-charset doesn’t
        # have any usable encodings (even encodings which will cause char
        # substitutions are considered usable; it’s non-ASCII with GET that
        # we don’t want).
        push @charsets, _encoding_ok(
            ($self->ownerDocument||next)->charset || next, $xwfu
        )
    }}

    if ($method ne "post") {
	require HTTP::Request;
	$uri = URI->new($uri, "http");
	$uri->can('query_form')
	 and $uri->query_form(@{_apply_charset \@charsets, \@form});
	return HTTP::Request->new(GET => $uri);
    }
    else  {
	require HTTP::Request::Common;
        if($xwfu) {
            my($charset,$form) = _apply_charset \@charsets, \@form;
            return HTTP::Request::Common::POST($uri, $form,
              Content_Type =>
                "application/x-www-form-urlencoded; charset=\"$charset\"");
        }
        else {
            my @new_form;
            while(@form) {
                my($name,$val) = (shift @form, shift @form);
#my $origval = $val;
                (my $charset, $val) = _apply_charset \@charsets, [$val];
#use DDS; Dump [$origval,$val, ];
                push @new_form, Encode'encode('MIME-B',$name),
                    ref $$val[0] ? $$val[0] : [(undef)x2,
                        Content_Type => "text/plain; charset=\"$charset\"",
                        Content => @$val,
                    ];
            }
            return HTTP::Request::Common::POST($uri, \@new_form,
                Content_Type => 'multipart/form-data'
            );
        }
    }
}

sub form
{
    package
	HTML::Form; # so caller tricks work
    my $self = shift;
    map { $_->form_name_value($self) } $self->inputs;
}




package HTML::DOM::NodeList::Radio; # solely for HTML::Form compatibility
                                    # Usually ::Input is used, but ::Radio
                                    # is for a set of radio buttons.
use Carp 'croak';
require HTML::DOM::NodeList;

our $VERSION = '0.048';
our @ISA = qw'HTML::DOM::NodeList';

sub type { 'radio' }

sub name { 
	my $ret = (my $self = shift)->item(0)->attr('name');
	if (@_) {
		$self->item($_)->attr(name=>@_) for 0..$self->length-1;
	}
	$ret
}

sub value { # ~~~ do case-folding and what-not, as in HTML::Form::ListInput
	my $self = shift;

	my $checked_elem;
	for (0..$self->length-1) {
		my $btn = $self->item($_);
		$btn->checked and
			$checked_elem = $btn, last;
	}

	if (@_) { for (0..$self->length-1) {
		my $btn = $self->item($_);
		$_[0] eq $btn->attr('value') and
		  $btn->disabled && croak(
		    "The value '$_[0]' has been disabled for field '${\
		     		     $self->name}'"
		  ),
		  $btn->checked(1),
		  last;
	}}

	$checked_elem && $checked_elem->attr('value')
}

sub possible_values {
	my $self = shift;
	map $self->item($_)->attr('value'), 0..$self->length-1
}

sub disabled {
	my $self = shift;
	for(@$self) {
		$_->disabled or return 0
	}
	return 1
}

sub form_name_value
# Pilfered from HTML::Form with slight changes.
{
    package
	HTML::Form::Input;
    my $self = shift;
    my $name = $self->name;
    return unless defined $name && length $name;
    return if $self->disabled;
    my $value = $self->value;
    return unless defined $value;
    return ($name => $value);
}


package HTML::DOM::Collection::Elements;

use strict;
use warnings;

use Scalar::Util 'weaken';

our $VERSION = '0.048';

require HTML::DOM::Collection;
our @ISA = 'HTML::DOM::Collection';

# Internals: \[$nodelist, $tie]

# Field constants:
sub nodelist(){0}
sub tye(){1}
sub seen(){2}     # whether this key has been seen
sub position(){3} # current (array) position used by NEXTKEY
sub ids(){4}      # whether we are iterating through ids
{ no warnings 'misc';
  undef &nodelist; undef &tye; undef &seen; undef &position;
}

sub namedItem {
	my($self, $name) = @_;
	my $list = $$self->[nodelist];
	my $elem;
	my @list;
	for(0..$list->length - 1) {
		no warnings 'uninitialized';
		push @list, $elem if 
			($elem = $list->item($_))->id eq $name
			  or
			$elem->attr('name') eq $name;
	}
	if(@list > 1) {
		# ~~~ Perhaps this should cache the new nodelist
		#     and return the same one each item. (Incident-
		#     ally, Firefox returns the same one but Safari
		#     makes a new one each time.)
		my $ret = HTML::DOM::NodeList::Magic->new(sub {
			no warnings 'uninitialized';
			grep $_->id eq $name ||
			     $_->attr('name') eq $name, @$list;
		});
		return $ret;
	}
	@list ? $list[0] :()
}



# ----------------- Docs ----------------- #


# ------- HTMLSelectElement interface ---------- #

package HTML::DOM::Element::Select;
our $VERSION = '0.048';
our @ISA = 'HTML::DOM::Element';

use overload fallback=>1, '@{}' => sub { shift->options };
	# ~~~ Don't I need %{} as well?

use Scalar'Util 'weaken';

sub type      { 'select-' . qw/one multiple/[!!shift->attr('multiple')] }
sub selectedIndex   {
	# Unfortunately, we cannot cache this (as in v. 0.040 and earlier)
	# as any change to the DOM will require it to be reset.
	my $self = shift;
	my $ret;
	if(defined wantarray) {
		my $x=0;
		# ~~~ I can optimise this by using $self->traverse since
		#     I don't need the rest of the list once I've found
		#     a selected item.
		for($self->options) {
			$_->selected and
				$ret = $x,
				last;
			$x++;
		}
		defined $ret or
			$ret = -1,
	}
	@_ and ($self->options)[$_[0]]->selected(1);
	return $ret;
}
sub value   { shift->options->value(@_) }
sub length { scalar(()= shift->options ) }
sub form           { 
 my $self = shift;
 my $ret = ($self->look_up(_tag => 'form'))[0] || $$self{_HTML_DOM_f}
  if defined wantarray;
 @_ and defined $_[0]
  ? ( weaken($$self{_HTML_DOM_f} = $_[0]), shift->add_element($self) )
  : (delete $$self{_HTML_DOM_f} or return $ret || ())
     ->remove_element($self);
 $ret || ();
}
sub options { # ~~~ I need to make this cache the resulting collection obj
              #     but when I do so I need to weaken references to $self
              #     and make ::Options do the same.
	my $self = shift;
	if (wantarray) {
		return grep tag $_ eq 'option', $self->descendants;
	}
	else {
		my $collection = HTML::DOM::Collection::Options->new(
		my $list = HTML::DOM::NodeList::Magic->new(
		    sub { grep tag $_ eq 'option', $self->descendants }
		), $self);
		$self->ownerDocument-> _register_magic_node_list($list);
		$collection;
	}
}
sub disabled  {
 shift->_attr( disabled => @_ ? $_[0] ? 'disabled' : undef : () )
}
sub multiple  {
 shift->_attr( multiple => @_ ? (undef,'multiple')[!!$_[0]] : () )
}
*name = \&HTML::DOM::Element::Form::name;
sub size      { shift->_attr( size => @_) }
sub tabIndex  { shift->_attr( tabindex => @_) }

sub add {
	my ($sel,$opt,$b4) = @_;
	# ~~~ does the following always work or will an optgroup break it?
	eval{$sel->insertBefore($opt,$b4)};
	return;
}
sub remove {
	my $self = shift;
	# ~~~ and how about this one?
	eval{$self->removeChild($self->options->item(shift) || return)};
	return;
}

sub blur { shift->trigger_event('blur') }
sub focus { shift->trigger_event('focus') }
sub _reset {  my $self = shift;
               $_->_reset for $self->options  }


package HTML::DOM::Collection::Options;

use strict;
use warnings;

our $VERSION = '0.048';

use Carp 'croak';
use constant sel => 5; # must not conflict with super
{ no strict 'refs'; delete ${__PACKAGE__."::"}{sel} } # after compilation

require HTML::DOM::Exception;
require HTML::DOM::Collection;
our @ISA = qw'HTML::DOM::Collection';

sub new {
	my $self = shift->SUPER::new(shift);
	$$$self[sel] = shift;
	$self
}

sub type { 'option' }
sub possible_values {
	map $_->value, @{+shift};
}

sub value { # ~~~ do case-folding and what-not, as in HTML::Form::ListInput
	my $self = shift;

	my $sel_elem;
	for (0..$self->length-1) {
		my $opt = $self->item($_);
		$opt->selected and
			$sel_elem = $opt, last;
	}

	if (@_) { for (0..$self->length-1) {
		my $opt = $self->item($_);
		my $v = $opt->value;
		$_[0] eq $v and
		  $opt->disabled && croak(
		    "The value '$_[0]' has been disabled for field '${\
		     		     $self->name}'"
		  ),
		  $opt->selected(1),
		  last;
	}}

	!defined $sel_elem # Shouldn't happen in well-formed documents, but
	    and $sel_elem  # how many documents are well-formed?
	     = $self->item(0);

	$sel_elem->value;
}

sub name {
	$${+shift}[sel]->name
}

sub disabled {
	(my $self = shift)->item(0)->look_up(_tag => 'select')->disabled 
		and return 1;
	for (@$self) {
		$_->disabled || return 0;
	}
	return 1
}

sub length { # override
	my $self = shift;
	die new HTML::DOM::Exception 
		HTML::DOM::Exception::NOT_SUPPORTED_ERR,
		"This implementation does not allow length to be set"
	if @_;
	$self->SUPER::length;
}

*form_name_value = \& HTML::DOM::NodeList::Radio::form_name_value;


# ------- HTMLOptGroupElement interface ---------- #

package HTML::DOM::Element::OptGroup;
our $VERSION = '0.048';
our @ISA = 'HTML::DOM::Element';

sub label  { shift->_attr( label => @_) }
*disabled = \&HTML::DOM::Element::Select::disabled;


# ------- HTMLOptionElement interface ---------- #

package HTML::DOM::Element::Option;
our $VERSION = '0.048';
our @ISA = qw'HTML::DOM::Element';

use Carp 'croak';
require HTML::DOM::Exception;

*form = \&HTML::DOM::Element::Select::form;
sub defaultSelected   {
 shift->_attr( selected => @_ ? $_[0] ? 'selected' : undef : () )
}

sub text { 
	shift->as_text
}

sub index {
	my $self = shift;
	my $indx = 0;
	my @options = (my $sel = $self->look_up(_tag => 'select'))
		->options;
	for(@options){
		last  if $self == $_;
		$indx++;
	}
	# This should not happen, unless the tree is horribly mangled:
	defined $indx or die new HTML::DOM::Exception 
	HTML::DOM::Exception::HIERARCHY_REQUEST_ERR,
	"It seems this option element is not a descendant of its ancestor."
	;
	if ( @_ ) {{
		my $new_indx= shift;
		last if $new_indx == $indx;
		if ($new_indx == 0) {
			$sel->insertBefore($self, $options[0]);
			last;
		}
		$options[$new_indx-1]->parentNode->insertBefore(
			$self, $options[$new_indx-1]->nextSibling
		);
	}}
	$indx;
}

*disabled = \&HTML::DOM::Element::Select::disabled;
*label = \&HTML::DOM::Element::OptGroup::label;

sub selected {
	my $self = shift;
	my $ret;

	if(!defined $self->{_HTML_DOM_sel}) {
		$ret = $self->attr('selected')||0;
	}
	else {
		$ret = $self->{_HTML_DOM_sel}
	}
	if(@_ && !$ret != !$_[0]) {
		my $sel = $self->look_up(_tag => 'select');
		if(!$sel || $sel->multiple) {
			$self->{_HTML_DOM_sel} = shift;
		}
		elsif($_[0]) { # You can't deselect the only selected
		               # option if exactly one option must be 
		               # selected at any given time.
			$self->{_HTML_DOM_sel} = shift;
			$_ != $self and $_->{_HTML_DOM_sel} = 0
				for $sel->options;
		}
	}
	$ret
}

sub value { # ~~~ do case-folding and what-not, as in HTML::Form::ListInput

	my $self = shift;
	my $ret;

	if(caller =~ /^(?:HTML::Form(?:::Input)?|WWW::Mechanize)\z/) {
		# ~~~ I can optimise this to call ->value once.
		$ret = $self->selected ? $self->value : undef;
		@_ and defined $_[0]
			? $_[0] eq $self->value
				? $self->selected(1)
				: croak "Invalid value '$_[0]' for option "
					. $self->name
			: $self->selected(0);
		return $ret;
	}

	defined($ret = $self->attr(value => @_)) or
		$ret = $self->text;

	return $ret;
}

sub type() { 'option' }

sub possible_values {
	(undef, shift->value)
}

sub name {
	shift->look_up(_tag => 'select')->name
}

sub _reset { delete shift->{_HTML_DOM_sel} }

*form_name_value = \& HTML::DOM::NodeList::Radio::form_name_value;


# ------- HTMLInputElement interface ---------- #

package HTML::DOM::Element::Input;
our $VERSION = '0.048';
our @ISA = qw'HTML::DOM::Element';

use Carp 'croak';

sub defaultValue   { shift->_attr( value => @_) }
sub defaultChecked {
 shift->_attr( checked => @_ ? $_[0] ? 'checked' : undef : () )
}
*form = \&HTML::DOM::Element::Select::form;
sub accept         { shift->_attr( accept => @_) }
sub accessKey      { shift->_attr( accesskey => @_) }
sub align          { lc shift->_attr( align => @_) }
sub alt            { shift->_attr( alt => @_) }
sub checked        {
	my $self = shift;
	my $ret;
	if(!defined $self->{_HTML_DOM_checked}) {
		$ret = $self->defaultChecked
	}
	else {
		$ret = $self->{_HTML_DOM_checked}
	}
	if(  @_ && !$ret != not $self->{_HTML_DOM_checked} = shift
	 and !$ret and $self->type eq 'radio'  ) {
		if(
		 my $form = $self->form and defined(my $name = $self->name)
		) {
			no warnings 'uninitialized';
			$_ != $self && $_->type eq 'radio'
			 && $_->name eq $name
			  and $_->{_HTML_DOM_checked} = 0
				for $form->elements;
		}
	}
	return $ret;
}
*disabled = \&HTML::DOM::Element::Select::disabled;
sub maxLength { shift->_attr( maxlength => @_) }
*name = \&HTML::DOM::Element::Form::name;
sub readOnly { shift->_attr(readonly => @_ ? $_[0]?'readonly':undef : ()) }
*size = \&HTML::DOM::Element::Select::size;
sub src       { shift->_attr( src => @_) }
*tabIndex = \&HTML::DOM::Element::Select::tabIndex;
sub type      {
	my $ret = shift->_attr('type', @_);
	return defined $ret ? lc $ret : 'text'
}
sub useMap    { shift->_attr( usemap => @_) }
sub value        {
	my $self = shift;
	my($ret,$type);

	if(caller =~ /^(?:HTML::Form(?:::Input)?|WWW::Mechanize)\z/ and
	    ($type = $self->type) =~ /^(?:button|reset)\z/ && return ||
	     $type eq 'checkbox') {
		# ~~~ Do case-folding as in HTML::Input::ListInput
		my $value = $self->value;
		length $value or $value = 'on';
		$ret = $self->checked
			? $value
			: undef;
		@_ and defined $_[0]
			? $_[0] eq $value
				? $self->checked(1)
				: croak
				   "Invalid value '$_[0]' for checkbox "
				   . $self->name
			: $self->checked(0);
		return $ret;
	}

# ~~~ shouldn't I make sure that modifying the value attribute 
#     (=defaultValue) leaves the value alone, even if the value has not
#     yet been accessed? (The same goes for checked and $option->selected)
	if(!defined $self->{_HTML_DOM_value}) {
		$ret = $self->defaultValue
	}
	else {
		$ret = $self->{_HTML_DOM_value}
	}
	@_ and $self->{_HTML_DOM_value} = shift;
	no warnings;
	return "$ret";
}
sub _reset {
	my $self = shift;
	$self->checked($self->defaultChecked);
	$self->value($self->defaultValue);
}

*blur = \&HTML::DOM::Element::Select::blur;
*focus = \&HTML::DOM::Element::Select::focus;
sub select { shift->trigger_event('select') }
sub click { for(shift){
	my(undef,$x,$y) = @_;
	defined or $_ = 1  for $x, $y;
	local($$_{_HTML_DOM_clicked}) = [$x,$y];
	$_->type eq 'checkbox' && $_->checked(!$_->checked);
	$_->trigger_event('click');
	return;
}}

sub trigger_event {
	my ($a,$evnt) = (shift,shift);
	my $input_type = $a->type;
	$a->SUPER::trigger_event(
		$evnt,
		$input_type =~ /^(?:(submi)|rese)t\z/
			?( DOMActivate_default =>
				# I’m not using a closure here, because we
				# don’t want the overhead of cloning it
				# when it might not even be used.
				(sub { (shift->target->form||return)
					->trigger_event('submit') },
				 sub { (shift->target->form||return)
					->trigger_event('reset') })
				  [!$1]
			) :(),
		@_
	);
}

sub possible_values {
	$_[0]->type eq 'checkbox' ? wantarray ? (undef, shift->value) : 2
	: ()
}
sub form_name_value
{
    my $self = shift;
    my $type = $self->type;
    if ($type =~ /^(image|submit)\z/) {
        return unless $self->{_HTML_DOM_clicked};
        if($1 eq 'image') {
            my $name = $self->name;
            $name = length $name ? "$name." : '';
            return "${name}x" => $self->{_HTML_DOM_clicked}[0],
                   "${name}y" => $self->{_HTML_DOM_clicked}[1]
        }
    }
    return $type eq 'file'
        ? $self->HTML_Form_FileInput_form_name_value(@_)
        : $self->HTML_Form_Input_form_name_value(@_);
}

# These two were stolen from HTML::Form with a few tweaks:
sub HTML_Form_Input_form_name_value
{
    package
	HTML::Form::Input;
    my $self = shift;
    my $name = $self->name;
    return unless defined $name && length $name;
    return if $self->disabled;
    my $value = $self->value;
    return unless defined $value;
    return ($name => $value);
}

sub HTML_Form_FileInput_form_name_value {
    package
	HTML::Form::ListInput;
    my($self, $form) = @_;
    return $self-> HTML_Form_Input_form_name_value($form)
	if uc $form->method ne "POST" ||
	   lc $form->enctype ne "multipart/form-data";

    my $name = $self->name;
    return unless defined $name;
    return if $self->{disabled};

    my $file = $self->file;
    my $filename = $self->filename;
    my @headers = $self->headers;
    my $content = $self->content;
    if (defined $content) {
	$filename = $file unless defined $filename;
	$file = undef;
	unshift(@headers, "Content" => $content);
    }
    elsif (!defined($file) || length($file) == 0) {
	return;
    }

    # legacy (this used to be the way to do it)
    if (ref($file) eq "ARRAY") {
	my $f = shift @$file;
	my $fn = shift @$file;
	push(@headers, @$file);
	$file = $f;
	$filename = $fn unless defined $filename;
    }

    return ($name => [$file, $filename, @headers]);
}



*file = \&value;

sub filename {
    my $self = shift;
    my $old = $self->{_HTML_DOM_filename};
    $self->{_HTML_DOM_filename} = shift if @_;
    $old = $self->file unless defined $old;
    $old;
}

sub headers { } # ~~~ Do I want to complete this?

sub content {
    my $self = shift;
    my $old = $self->{_HTML_DOM_content};
    $self->{_HTML_DOM_content} = shift if @_;
    $old;
}



# ------- HTMLTextAreaElement interface ---------- #

package HTML::DOM::Element::TextArea;
our $VERSION = '0.048';
our @ISA = qw'HTML::DOM::Element';

sub defaultValue { # same as HTML::DOM::Element::Title::text
	($_[0]->firstChild or
		@_ > 1 && $_[0]->appendChild(
			shift->ownerDocument->createTextNode(shift)
		),
		return '',
	)->data(@_[1..$#_]);
}
*form = \&HTML::DOM::Element::Select::form;
*accessKey = \&HTML::DOM::Element::Input::accessKey;
sub cols      { shift->_attr( cols       => @_) }
*disabled = \&HTML::DOM::Element::Select::disabled;
*name = \&HTML::DOM::Element::Select::name;
*readOnly = \&HTML::DOM::Element::Input::readOnly;
sub rows {shift->_attr( rows      => @_) }
*tabIndex = \&HTML::DOM::Element::Select::tabIndex;
sub type { 'textarea' }
sub value        {
	my $self = shift;
	my $ret;

	if(!defined $self->{_HTML_DOM_value}) {
		$ret = $self->defaultValue
	}
	else {
		$ret = $self->{_HTML_DOM_value}
	}
	@_ and $self->{_HTML_DOM_value} = shift;
	return $ret;
}
*blur = \&HTML::DOM::Element::Select::blur;
*focus = \&HTML::DOM::Element::Select::focus;
*select = \&HTML::DOM::Element::Input::select;

sub _reset {
	my $self = shift;
	$self->value($self->defaultValue);
}

*form_name_value = \& HTML::DOM::NodeList::Radio::form_name_value;


# ------- HTMLButtonElement interface ---------- #

package HTML::DOM::Element::Button;
our $VERSION = '0.048';
our @ISA = qw'HTML::DOM::Element';

*form = \&HTML::DOM::Element::Select::form;
*accessKey = \&HTML::DOM::Element::Input::accessKey;
*disabled = \&HTML::DOM::Element::Select::disabled;
*name = \&HTML::DOM::Element::Form::name;
*tabIndex = \&HTML::DOM::Element::Select::tabIndex;
sub type       { lc shift->attr('type') }
sub value      { shift->attr( value       => @_) }


# ------- HTMLLabelElement interface ---------- #

package HTML::DOM::Element::Label;
our $VERSION = '0.048';
our @ISA = qw'HTML::DOM::Element';

*form = \&HTML::DOM::Element::Select::form;
*accessKey = \&HTML::DOM::Element::Input::accessKey;
sub htmlFor { shift->_attr( for       => @_) }

# ------- HTMLFieldSetElement interface ---------- #

package HTML::DOM::Element::FieldSet;
our $VERSION = '0.048';
our @ISA = qw'HTML::DOM::Element';

*form = \&HTML::DOM::Element::Select::form;

# ------- HTMLLegendElement interface ---------- #

package HTML::DOM::Element::Legend;
our $VERSION = '0.048';
our @ISA = qw'HTML::DOM::Element';

*form = \&HTML::DOM::Element::Select::form;
*accessKey = \&HTML::DOM::Element::Input::accessKey;
*align = \*HTML::DOM::Element::Input::align;


no warnings;
!+~()#%$-*