line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Mojo::DOM::Role::Form; |
2
|
|
|
|
|
|
|
|
3
|
2
|
|
|
2
|
|
2345
|
use Mojo::Base -role; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
12
|
|
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
requires qw{ancestors at attr find matches selector tag val}; |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
sub target { |
8
|
21
|
|
|
21
|
1
|
2733
|
my ($self, $submit) = (shift, shift); |
9
|
21
|
100
|
100
|
|
|
73
|
return () if ($self->tag // '') ne 'form'; |
10
|
|
|
|
|
|
|
return () |
11
|
20
|
100
|
100
|
|
|
360
|
unless defined($submit = $self->at($submit || _form_default_submit($self))); |
12
|
19
|
100
|
|
|
|
14964
|
return () if $submit->matches('[disabled]'); |
13
|
|
|
|
|
|
|
return ( |
14
|
17
|
|
100
|
|
|
2535
|
uc($submit->attr('formmethod') || $self->attr('method') || 'GET'), |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
15
|
|
|
|
|
|
|
$submit->attr('formaction') || $self->attr('action') || '#', |
16
|
|
|
|
|
|
|
$submit->attr('formenctype') || $self->attr('enctype') || 'url-encoded' |
17
|
|
|
|
|
|
|
); |
18
|
|
|
|
|
|
|
} |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
around val => sub { |
21
|
|
|
|
|
|
|
my ($orig, $self, @args) = @_; |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
# "form" |
24
|
|
|
|
|
|
|
return $self->find('button, checkbox, input, radio, select, textarea')->map( |
25
|
|
|
|
|
|
|
sub { |
26
|
|
|
|
|
|
|
my $is_image = !!$_->matches('input[type=image]'); |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
# ignore disabled nodes |
29
|
|
|
|
|
|
|
return () if _form_element_disabled($_); |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
# ignore those without name, unless image type |
32
|
|
|
|
|
|
|
return () if !defined(my $name = $_->attr("name")) && !$is_image; |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
# only continue if the clickable element matches (synthesize click) |
35
|
|
|
|
|
|
|
return () if _form_element_submits($_) && !$_->matches($_[1]); |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
# client only buttons ignored |
38
|
|
|
|
|
|
|
return () if _form_element_client_only_button($_); |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
# simply return name => value for all but image types |
41
|
|
|
|
|
|
|
return [$name => $_->val()] unless $is_image; |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
# synthesize image click |
44
|
|
|
|
|
|
|
return _form_image_click($_, $name); |
45
|
|
|
|
|
|
|
}, |
46
|
|
|
|
|
|
|
$args[0] || _form_default_submit($self) |
47
|
|
|
|
|
|
|
)->reduce( |
48
|
|
|
|
|
|
|
sub { |
49
|
|
|
|
|
|
|
my ($key, $value) = @$b; |
50
|
|
|
|
|
|
|
$a->{$key} |
51
|
|
|
|
|
|
|
= defined $a->{$key} && defined($value) |
52
|
|
|
|
|
|
|
? [ref($a->{$key}) ? (@{$a->{$key}}, $value) : ($a->{$key}, $value)] |
53
|
|
|
|
|
|
|
: $value; |
54
|
|
|
|
|
|
|
$a; |
55
|
|
|
|
|
|
|
}, |
56
|
|
|
|
|
|
|
{} |
57
|
|
|
|
|
|
|
) if (my $tag = $self->tag) eq 'form'; |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
# "option" |
60
|
|
|
|
|
|
|
return $self->{value} // $self->text if $tag eq 'option'; |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
# "input" ("type=checkbox" and "type=radio") |
63
|
|
|
|
|
|
|
my $type = $self->{type} // ''; |
64
|
|
|
|
|
|
|
return $self->{value} // 'on' |
65
|
|
|
|
|
|
|
if $tag eq 'input' && ($type eq 'radio' || $type eq 'checkbox'); |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
# "textarea", "input" or "button". Give input[type=submit] default value |
68
|
|
|
|
|
|
|
return ( |
69
|
|
|
|
|
|
|
$tag eq 'textarea' ? $self->text |
70
|
|
|
|
|
|
|
: ( $self->matches('input[type=submit]') ? ($self->{value} || 'Submit') |
71
|
|
|
|
|
|
|
: $self->{value}) |
72
|
|
|
|
|
|
|
) if $tag ne 'select'; |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
# "select" |
75
|
|
|
|
|
|
|
my $v = $self->find('option:checked:not([disabled])') |
76
|
|
|
|
|
|
|
->grep(sub { !$_->ancestors('optgroup[disabled]')->size })->map('val'); |
77
|
|
|
|
|
|
|
return exists $self->{multiple} ? $v->size ? $v->to_array : undef : $v->last; |
78
|
|
|
|
|
|
|
}; |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
# |
81
|
|
|
|
|
|
|
# internal |
82
|
|
|
|
|
|
|
# |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
sub _form_default_submit { |
85
|
|
|
|
|
|
|
return shift->find('*') |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
# filter for those submittable nodes |
88
|
454
|
|
|
454
|
|
23974
|
->grep(sub { !!$_->_form_element_submits; }) |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
# only the first continues, save some cycles |
91
|
30
|
|
|
30
|
|
743
|
->tap(sub { splice @$_, 1; }) |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
# get the selector and relativise to form |
94
|
|
|
|
|
|
|
->map(sub { |
95
|
23
|
|
|
23
|
|
341
|
(my $s = $_->selector) =~ s/^.*form[^>]*>\s//; |
96
|
23
|
|
|
|
|
4057
|
return $s; |
97
|
30
|
|
100
|
30
|
|
31218
|
})->first || ''; |
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
sub _form_element_client_only_button { |
101
|
222
|
|
|
222
|
|
376
|
my $s = 'input[type=button], button[type=button], button[type=reset]'; |
102
|
222
|
|
|
|
|
600
|
return !!$_[0]->matches($s); |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
sub _form_element_disabled { |
106
|
441
|
100
|
|
441
|
|
76953
|
return 1 if $_[0]->matches('[disabled]'); |
107
|
399
|
100
|
100
|
|
|
46057
|
return 1 |
108
|
|
|
|
|
|
|
if $_[0]->ancestors('fieldset[disabled]')->size |
109
|
|
|
|
|
|
|
&& !$_[0]->ancestors('fieldset legend:first-child')->size; |
110
|
396
|
|
|
|
|
224579
|
return 0; |
111
|
|
|
|
|
|
|
} |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
sub _form_element_submits { |
114
|
719
|
|
|
719
|
|
1181
|
my $s = join ', ', 'button:not([type=button], [type=reset])', 'button', |
115
|
|
|
|
|
|
|
'input[type=submit]', 'input[type=image]'; |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
# submit is the default |
118
|
719
|
100
|
100
|
|
|
1527
|
return 1 if $_[0]->matches($s) && !_form_element_disabled($_[0]); |
119
|
588
|
|
|
|
|
304590
|
return 0; |
120
|
|
|
|
|
|
|
} |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
sub _form_image_click { |
123
|
6
|
|
|
6
|
|
12
|
my ($self, $name) = (shift, shift); |
124
|
6
|
|
100
|
|
|
15
|
my ($x, $y) = map { int(rand($self->attr($_) || 1)) + 1 } qw{width height}; |
|
12
|
|
|
|
|
165
|
|
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
# x and y if no name |
127
|
6
|
100
|
|
|
|
101
|
return ([x => $x], [y => $y]) unless $name; |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
# named x and y, with name |
130
|
3
|
|
|
|
|
18
|
return (["$name.x" => $x], ["$name.y" => $y]); |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
1; |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
=encoding utf8 |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
=head1 NAME |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
Mojo::DOM::Role::Form - Form data extraction |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
=head1 SYNOPSIS |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
# description |
144
|
|
|
|
|
|
|
my $obj = Mojo::DOM::Role::Form->new(); |
145
|
|
|
|
|
|
|
$obj->target('#submit-id'); |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
=head1 DESCRIPTION |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
L based role to compose additional form data extraction methods into |
150
|
|
|
|
|
|
|
L. |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
=head1 METHODS |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
L implements the following methods. |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
=head2 target |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
# result |
159
|
|
|
|
|
|
|
$obj->target |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
Explain what the L"target"> does. |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
=head1 AUTHOR |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
=cut |