File Coverage

blib/lib/HTML/FillInForm.pm
Criterion Covered Total %
statement 244 252 96.8
branch 177 194 91.2
condition 66 85 77.6
subroutine 18 18 100.0
pod 8 11 72.7
total 513 560 91.6


line stmt bran cond sub pod time code
1             package HTML::FillInForm;
2              
3 26     26   176414 use integer; # no floating point math so far!
  26         355  
  26         127  
4 26     26   829 use strict; # and no funny business, either.
  26         51  
  26         1076  
5              
6 26     26   166 use Carp; # generate better errors with more context
  26         50  
  26         2891  
7              
8              
9             # required for UNIVERSAL->can
10             require 5.005;
11              
12 26     26   330 use vars qw($VERSION @ISA);
  26         42  
  26         154881  
13             $VERSION = '2.21';
14              
15              
16             sub new {
17 57     57 1 48372 my $class = shift;
18 57         174 my $self = bless {}, $class;
19              
20             # required for attr_encoded
21              
22 57   66     384 my %arg = @_ || ();
23 57   50     288 my $parser_class = $arg{parser_class} || 'HTML::Parser';
24 57 50       3990 eval "require $parser_class;" || die "require $parser_class failed: $@";
25 57         189053 @ISA = ($parser_class);
26              
27 57         444 $self->init(@_);
28              
29 57 50       3456 unless ($self->can('attr_encoded')) {
30 0         0 die "attr_encoded method is missing. If are using HTML::Parser, you need at least version 3.26";
31             }
32              
33             # tell HTML::Parser not to decode attributes
34 57         175 $self->attr_encoded(1);
35              
36 57         303 return $self;
37             }
38              
39             # a few shortcuts to fill()
40 1     1 0 3 sub fill_file { my $self = shift; return $self->fill('file' ,@_); }
  1         3  
41 1     1 0 2 sub fill_arrayref { my $self = shift; return $self->fill('arrayref' ,@_); }
  1         3  
42 3     3 0 6 sub fill_scalarref { my $self = shift; return $self->fill('scalarref',@_); }
  3         10  
43              
44             # track the keys we support. Useful for file-name detection.
45             sub _known_keys {
46             return {
47 48     48   497 scalarref => 1,
48             arrayref => 1,
49             fdat => 1,
50             fobject => 1,
51             file => 1,
52             target => 1,
53             fill_password => 1,
54             ignore_fields => 1,
55             disable_fields => 1,
56             invalid_fields => 1,
57             invalid_class => 1,
58             }
59             }
60              
61             sub fill {
62 59     59 1 16229 my $self = shift;
63              
64             # If we are called as a class method, go ahead and call new().
65 59 100       212 $self = $self->new if (not ref $self);
66              
67 59         87 my %option;
68              
69             # If the first arg is a scalarref, translate that to scalarref => $first_arg
70 59 100       410 if (ref $_[0] eq 'SCALAR') {
    100          
    100          
    50          
    100          
71 9         22 $option{scalarref} = shift;
72             }
73             elsif (ref $_[0] eq 'ARRAY') {
74 1         2 $option{arrayref} = shift;
75             }
76             elsif (ref $_[0] eq 'GLOB') {
77 1         3 $option{file} = shift;
78             }
79             elsif (ref $_[0]) {
80 0         0 croak "data source is not a reference type we understand";
81             }
82             # Last chance, if the first arg isn't one of the known keys, we
83             # assume it is a file name.
84             elsif (not _known_keys()->{$_[0]} ) {
85 1         3 $option{file} = shift;
86             }
87             else {
88             # Should be a known key. Nothing to do.
89             }
90              
91              
92             # Now, check to see if the next arg is also a reference.
93 59         155 my $data;
94 59 100       212 if (ref $_[0]) {
95 8         13 $data = shift;
96 8 50       33 $data = [$data] unless ref $data eq 'ARRAY';
97              
98 8         17 for my $source (@$data) {
99 8 100       29 if (ref $source eq 'HASH') {
    50          
    0          
100 6         8 push @{ $option{fdat} }, $source;
  6         23  
101             }
102             elsif (ref $source) {
103 2 50       15 if ($source->can('param')) {
104 2         28 push @{ $option{fobject} }, $source;
  2         9  
105             }
106             else {
107 0         0 croak "data source $source does not supply a param method";
108             }
109             }
110             elsif (defined $source) {
111 0         0 croak "data source $source is not a hash or object reference";
112             }
113             }
114              
115             }
116              
117            
118             # load in the rest of the options
119 59         229 %option = (%option, @_);
120              
121              
122             # As suggested in the docs, merge multiple fdats into one.
123 59 100       199 if (ref $option{fdat} eq 'ARRAY') {
124 6         7 my %merged;
125 6         9 for my $hash (@{ $option{fdat} }) {
  6         13  
126 6         18 for my $key (keys %$hash) {
127 16         37 $merged{$key} = $hash->{$key};
128             }
129             }
130 6         16 $option{'fdat'} = \%merged;
131             }
132              
133              
134 59         83 my %ignore_fields;
135 6         21 %ignore_fields = map { $_ => 1 } ( ref $option{'ignore_fields'} eq 'ARRAY' )
  2         6  
136 59 100       178 ? @{ $option{ignore_fields} } : $option{ignore_fields} if exists( $option{ignore_fields} );
    100          
137 59         484 $self->{ignore_fields} = \%ignore_fields;
138              
139 59         186 my %disable_fields;
140 2         9 %disable_fields = map { $_ => 1 } ( ref $option{'disable_fields'} eq 'ARRAY' )
  1         2  
141 59 100       179 ? @{ $option{disable_fields} } : $option{disable_fields} if exists( $option{disable_fields} );
    100          
142 59         111 $self->{disable_fields} = \%disable_fields;
143              
144 59         77 my %invalid_fields;
145 8         19 %invalid_fields = map { $_ => 1 } ( ref $option{'invalid_fields'} eq 'ARRAY' )
  3         7  
146 59 50       197 ? @{ $option{invalid_fields} } : $option{invalid_fields} if exists( $option{invalid_fields} );
    100          
147 59         129 $self->{invalid_fields} = \%invalid_fields;
148              
149 59 100       180 if (my $fdat = $option{fdat}){
150             # Copy the structure to prevent side-effects.
151 42         54 my %copy;
152 42         102 keys %$fdat; # reset fdat if each or Dumper was called on fdat
153 42         187 while(my($key, $val) = each %$fdat) {
154 73 100       170 next if exists $ignore_fields{$key};
155 72 100       355 $copy{ $key } = ref $val eq 'ARRAY' ? [ @$val ] : $val;
156             }
157 42         94 $self->{fdat} = \%copy;
158             }
159              
160             # We want the reference to these objects to go out of scope at the
161             # end of the method.
162 59         157 local $self->{objects} = [];
163 59 100       552 if(my $objects = $option{fobject}){
164 16 100       47 unless(ref($objects) eq 'ARRAY'){
165 13         26 $objects = [ $objects ];
166             }
167 16         37 for my $object (@$objects){
168             # make sure objects in 'param_object' parameter support param()
169 17 100       297 defined($object->can('param')) or
170             croak("HTML::FillInForm->fill called with fobject option, containing object of type " . ref($object) . " which lacks a param() method!");
171             }
172              
173 15         181 $self->{objects} = $objects;
174             }
175 58 100       199 if (my $target = $option{target}){
176 2         4 $self->{'target'} = $target;
177             }
178              
179 58 100       161 if (my $invalid_class = $option{invalid_class}){
180 1         3 $self->{'invalid_class'} = $invalid_class;
181             } else {
182 57         139 $self->{'invalid_class'} = 'invalid';
183             }
184              
185 58 100       206 if (defined($option{fill_password})){
186 1         3 $self->{fill_password} = $option{fill_password};
187             } else {
188 57         160 $self->{fill_password} = 1;
189             }
190              
191 58         321 $self->{clear_absent_checkboxes} = $option{clear_absent_checkboxes};
192              
193             # make sure method has data to fill in HTML form with!
194 58 50 66     310 unless(exists $self->{fdat} || $self->{objects}){
195 0         0 croak("HTML::FillInForm->fillInForm() called without 'fobject' or 'fdat' parameter set");
196             }
197              
198 58         159 local $self->{object_param_cache};
199              
200 58 100       227 if(my $file = $option{file}){
    100          
    100          
201 3         14 $self->parse_file($file);
202             } elsif (my $scalarref = $option{scalarref}){
203 52         1009 $self->parse($$scalarref);
204             } elsif (my $arrayref = $option{arrayref}){
205 2         4 for (@$arrayref){
206 10         67 $self->parse($_);
207             }
208             }
209              
210 58         347 $self->eof;
211 58         503 return delete $self->{output};
212             }
213              
214             # handles opening HTML tags such as
215             sub start {
216 291     291 1 716 my ($self, $tagname, $attr, $attrseq, $origtext) = @_;
217              
218             # set the current form
219 291 100       615 if ($tagname eq 'form') {
220 24         46 $self->{object_param_cache} = {};
221 24 100 100     126 if (exists $attr->{'name'} || exists $attr->{'id'}) {
222 6   66     21 $self->{'current_form'} = $attr->{'name'} || $attr->{'id'};
223             } else {
224             # in case of previous one without
225 18         35 delete $self->{'current_form'};
226             }
227             }
228              
229             # This form is not my target.
230 291 100 100     752 if (exists $self->{'target'} &&
      66        
231             (! exists $self->{'current_form'} ||
232             $self->{'current_form'} ne $self->{'target'})) {
233 12         18 $self->{'output'} .= $origtext;
234 12         65 return;
235             }
236            
237             # HTML::Parser converts tagname to lowercase, so we don't need /i
238 279 100       628 if ($self->{option_no_value}) {
239 3         6 $self->{output} .= '>';
240 3         8 delete $self->{option_no_value};
241             }
242              
243             # Check if we need to disable this field
244 279 50 100     1243 $attr->{disabled} = 'disabled'
      66        
      33        
      66        
245             if exists $attr->{'name'} and
246             exists $self->{disable_fields}{ $attr->{'name'} } and
247             $self->{disable_fields}{ $attr->{'name'} } and
248             not ( exists $attr->{disabled} and $attr->{disabled} );
249              
250             # Check if we need to invalidate this field
251 279         334 my $invalidating = 0;
252 279 100 100     1282 if (exists $attr->{name} and
      66        
253             exists $self->{invalid_fields}{ $attr->{name} } and
254             $self->{invalid_fields}{ $attr->{name} }) {
255 8         9 $invalidating = 1;
256 8 100 66     25 if (exists $attr->{class} and length $attr->{class}) {
257             # don't add the class if it's already there
258 3 100       42 unless ($attr->{class} =~ /\b\Q$self->{invalid_class}\E\b/) {
259 2         6 $attr->{class} .= " $self->{invalid_class}";
260             }
261             } else {
262 5         11 $attr->{class} = $self->{invalid_class};
263             }
264             }
265              
266 279 100       728 if ($tagname eq 'input'){
    100          
    100          
    100          
267 123 100       418 my $value = exists $attr->{'name'} ? $self->_get_param($attr->{'name'}) : undef;
268             # force hidden fields to have a value
269 123 100 100     731 $value = '' if exists($attr->{'type'}) && $attr->{'type'} eq 'hidden' && ! exists $attr->{'value'} && ! defined $value;
      100        
      100        
270              
271             # browsers do not pass unchecked checkboxes at all, so hack around
272 123 50 100     434 $value = '' if $self->{clear_absent_checkboxes} && !defined $value && exists($attr->{'type'}) && ($attr->{'type'} eq 'checkbox' || $attr->{'type'} eq 'radio');
      66        
      66        
      66        
273 123 100       254 if (defined($value)){
274             # check for input type, noting that default type is text
275 98 100 100     927 if (!exists $attr->{'type'} ||
    100 100        
    100          
    100          
276             $attr->{'type'} =~ /^(text|textfield|hidden|tel|search|url|email|datetime|date|month|week|time|datetime\-local|number|range|color|)$/i){
277 43 100       125 if ( ref($value) eq 'ARRAY' ) {
278 7         12 $value = shift @$value;
279 7 100       17 $value = '' unless defined $value;
280             }
281 43         100 $attr->{'value'} = __escapeHTML($value);
282             } elsif (lc $attr->{'type'} eq 'password' && $self->{fill_password}) {
283 3 100       9 if ( ref($value) eq 'ARRAY' ) {
284 2         3 $value = shift @$value;
285 2 100       6 $value = '' unless defined $value;
286             }
287 3         10 $attr->{'value'} = __escapeHTML($value);
288             } elsif (lc $attr->{'type'} eq 'radio'){
289 22 100       52 if ( ref($value) eq 'ARRAY' ) {
290 4         6 $value = $value->[0];
291 4 100       9 $value = '' unless defined $value;
292             }
293             # value for radio boxes default to 'on', works with netscape
294 22 100       63 $attr->{'value'} = 'on' unless exists $attr->{'value'};
295 22 100       51 if ($attr->{'value'} eq __escapeHTML($value)){
296 4         17 $attr->{'checked'} = 'checked';
297             } else {
298 18         106 delete $attr->{'checked'};
299             }
300             } elsif (lc $attr->{'type'} eq 'checkbox'){
301             # value for checkboxes default to 'on', works with netscape
302 29 100       68 $attr->{'value'} = 'on' unless exists $attr->{'value'};
303              
304 29         36 delete $attr->{'checked'}; # Everything is unchecked to start
305 29 100       94 $value = [ $value ] unless ref($value) eq 'ARRAY';
306 29         203 foreach my $v ( @$value ) {
307 35 100       67 if ( $attr->{'value'} eq __escapeHTML($v) ) {
308 10         35 $attr->{'checked'} = 'checked';
309             }
310             }
311             # } else {
312             # warn(qq(Input field of unknown type "$attr->{type}": $origtext));
313             }
314             }
315 123         286 $self->{output} .= "<$tagname";
316 123         430 while (my ($key, $value) = each %$attr) {
317 401 100       945 next if $key eq '/';
318 392         1859 $self->{output} .= sprintf qq( %s="%s"), $key, $value;
319             }
320             # extra space put here to work around Opera 6.01/6.02 bug
321 123 100       284 $self->{output} .= ' /' if $attr->{'/'};
322 123         900 $self->{output} .= ">";
323             } elsif ($tagname eq 'option'){
324 78         181 my $value = $self->_get_param($self->{selectName});
325              
326             # browsers do not pass selects with no selected options at all,
327             # so hack around
328 78 100 100     300 $value = '' if $self->{clear_absent_checkboxes} && !defined $value;
329              
330 78 100       339 $value = [ $value ] unless ( ref($value) eq 'ARRAY' );
331              
332 78 100       174 if ( defined $value->[0] ){
333 54 100       115 delete $attr->{selected} if exists $attr->{selected};
334            
335 54 100       94 if(defined($attr->{'value'})){
336             # option tag has value attr -
337            
338 30 100       65 if ($self->{selectMultiple}){
339             # check if the option tag belongs to a multiple option select
340 27         36 foreach my $v ( grep { defined } @$value ) {
  36         90  
341 36 100       70 if ( $attr->{'value'} eq __escapeHTML($v) ){
342 8         26 $attr->{selected} = 'selected';
343             }
344             }
345             } else {
346             # if not every value of a fdat ARRAY belongs to a different select tag
347 3 50       9 if (not $self->{selectSelected}){
348 3 100       9 if ( $attr->{'value'} eq __escapeHTML($value->[0])){
349 2 50       9 shift @$value if ref($value) eq 'ARRAY';
350 2         5 $attr->{selected} = 'selected';
351 2         5 $self->{selectSelected} = 1; # remember that an option tag is selected for this select tag
352             }
353             }
354             }
355             } else {
356             # option tag has no value attr -
357             # save for processing under text handler
358 24         41 $self->{option_no_value} = __escapeHTML($value);
359             }
360             }
361 78         147 $self->{output} .= "<$tagname";
362 78         258 while (my ($key, $value) = each %$attr) {
363 61         282 $self->{output} .= sprintf qq( %s="%s"), $key, $value;
364             }
365 78 100       293 unless ($self->{option_no_value}){
366             # we can close option tag here
367 54         384 $self->{output} .= ">";
368             }
369             } elsif ($tagname eq 'textarea'){
370             # need to re-output the ->
385             # we need to set outputText to 'no' so that 'foobar' won't be printed
386 6         12 $self->{outputText} = 'no';
387 6         13 $self->{output} .= __escapeHTML($value);
388             }
389              
390             } elsif ($tagname eq 'select'){
391 29         66 $self->{selectName} = $attr->{'name'};
392 29 100       65 if (defined $attr->{'multiple'}){
393 20         31 $self->{selectMultiple} = 1; # helper var to remember if the select tag has the multiple attr set or not
394             } else {
395 9         19 $self->{selectMultiple} = 0;
396 9         13 $self->{selectSelected} = 0; # helper var to remember if an option was already selected in the current select tag
397             }
398              
399             # need to re-output the
400             # (doesn't disable need this too?)
401 29 100       53 if ($invalidating) {
402 1         3 $self->{output} .= "<$tagname";
403 1         5 while (my ($key, $value) = each %$attr) {
404 2         9 $self->{output} .= sprintf qq( %s="%s"), $key, $value;
405             }
406 1         8 $self->{output} .= ">";
407             } else {
408 28         213 $self->{output} .= $origtext;
409             }
410             } else {
411 38         266 $self->{output} .= $origtext;
412             }
413             }
414              
415             sub _get_param {
416 209     209   295 my ($self, $param) = @_;
417              
418 209 100       502 return undef if $self->{ignore_fields}{$param};
419              
420 203 100       604 return $self->{fdat}{$param} if exists $self->{fdat}{$param};
421              
422 115 100       310 return $self->{object_param_cache}{$param} if exists $self->{object_param_cache}{$param};
423              
424             # traverse the list in reverse order for backwards compatibility
425             # with the previous implementation.
426 80         94 for my $o (reverse @{$self->{objects}}) {
  80         174  
427 41         130 my @v = $o->param($param);
428              
429 41 100       834 next unless @v;
430              
431 21 100       114 return $self->{object_param_cache}{$param} = @v > 1 ? \@v : $v[0];
432             }
433              
434 59         153 return undef;
435             }
436              
437             # handles non-html text
438             sub text {
439 350     350 1 614 my ($self, $origtext) = @_;
440             # just output text, unless replaced value of
463             sub end {
464 138     138 1 214 my ($self, $tagname, $origtext) = @_;
465 138 50       296 if ($self->{option_no_value}) {
466 0         0 $self->{output} .= '>';
467 0         0 delete $self->{option_no_value};
468             }
469 138 100       398 if($tagname eq 'select'){
    100          
    100          
470 29         59 delete $self->{selectName};
471             } elsif ($tagname eq 'textarea'){
472 11         20 delete $self->{outputText};
473             } elsif ($tagname eq 'form') {
474 24         34 delete $self->{'current_form'};
475             }
476 138         616 $self->{output} .= $origtext;
477             }
478              
479             sub __escapeHTML {
480 172     172   234 my ($toencode) = @_;
481              
482 172 50       331 return undef unless defined($toencode);
483 172         283 $toencode =~ s/&/&/g;
484 172         210 $toencode =~ s/\"/"/g;
485 172         209 $toencode =~ s/>/>/g;
486 172         200 $toencode =~ s/
487 172         528 return $toencode;
488             }
489              
490             sub comment {
491 4     4 1 24 my ( $self, $text ) = @_;
492             # if it begins with '[if ' and doesn't end with '
493             # it's a "downlevel-revealed" conditional comment (stupid IE)
494             # or
495             # if it ends with '[endif]' then it's the end of a
496             # "downlevel-revealed" conditional comment
497 4 50 33     27 if(
      33        
498             (
499             ( index($text, '[if ') == 0 )
500             &&
501             ( $text !~ /
502             )
503             ||
504             ( $text eq '[endif]' )
505             ) {
506 0         0 $self->{output} .= '';
507             } else {
508 4         17 $self->{output} .= '';
509             }
510             }
511              
512             sub process {
513 4     4 1 7 my ( $self, $token0, $text ) = @_;
514 4         20 $self->{output} .= $text;
515             }
516              
517             sub declaration {
518 1     1 1 13 my ( $self, $text ) = @_;
519 1         14 $self->{output} .= '';
520             }
521              
522             1;
523              
524             __END__