]> Dogcows Code - chaz/p5-CGI-Ex/blobdiff - lib/CGI/Ex/Fill.pm
CGI::Ex 2.00
[chaz/p5-CGI-Ex] / lib / CGI / Ex / Fill.pm
index e1094ef929918320c053831a5d614f230dd8ddfc..662c0d9a7ee823be96450a62aeb329d9bc559011 100644 (file)
@@ -1,41 +1,46 @@
 package CGI::Ex::Fill;
 
-### CGI Extended Form Filler
+=head1 NAME
+
+CGI::Ex::Fill - Fast but compliant regex based form filler
+
+=cut
 
 ###----------------------------------------------------------------###
-#  Copyright 2003 - Paul Seamons                                     #
+#  Copyright 2006 - Paul Seamons                                     #
 #  Distributed under the Perl Artistic License without warranty      #
 ###----------------------------------------------------------------###
 
-### See perldoc at bottom
-
 use strict;
 use vars qw($VERSION
-            @ISA @EXPORT @EXPORT_OK
+            @EXPORT @EXPORT_OK
             $REMOVE_SCRIPT
             $REMOVE_COMMENT
             $MARKER_SCRIPT
             $MARKER_COMMENT
             $OBJECT_METHOD
-            $TEMP_TARGET
+            $_TEMP_TARGET
             );
-use Exporter;
+use base qw(Exporter);
 
-$VERSION   = '1.3';
-@ISA       = qw(Exporter);
-@EXPORT    = qw(form_fill);
-@EXPORT_OK = qw(form_fill html_escape get_tagval_by_key swap_tagval_by_key);
+BEGIN {
+    $VERSION   = '2.00';
+    @EXPORT    = qw(form_fill);
+    @EXPORT_OK = qw(fill form_fill html_escape get_tagval_by_key swap_tagval_by_key);
+};
 
 ### These directives are used to determine whether or not to
 ### remove html comments and script sections while filling in
 ### a form.  Default is on.  This may give some trouble if you
 ### have a javascript section with form elements that you would
 ### like filled in.
-$REMOVE_SCRIPT  = 1;
-$REMOVE_COMMENT = 1;
-$MARKER_SCRIPT  = "\0SCRIPT\0";
-$MARKER_COMMENT = "\0COMMENT\0";
-$OBJECT_METHOD  = "param";
+BEGIN {
+    $REMOVE_SCRIPT  = 1;
+    $REMOVE_COMMENT = 1;
+    $MARKER_SCRIPT  = "\0SCRIPT\0";
+    $MARKER_COMMENT = "\0COMMENT\0";
+    $OBJECT_METHOD  = "param";
+};
 
 ###----------------------------------------------------------------###
 
@@ -47,339 +52,366 @@ $OBJECT_METHOD  = "param";
 ### pos4 - boolean fill in password fields - default is true
 ### pos5 - hashref or arrayref of fields to ignore
 sub form_fill {
-  my $text          = shift;
-  my $ref           = ref($text) ? $text : \$text;
-  my $form          = shift;
-  my $forms         = UNIVERSAL::isa($form, 'ARRAY') ? $form : [$form];
-  my $target        = shift;
-  my $fill_password = shift;
-  my $ignore        = shift || {};
-  $ignore = {map {$_ => 1} @$ignore} if UNIVERSAL::isa($ignore, 'ARRAY');
-  $fill_password = 1 if ! defined $fill_password;
-
-
-  ### allow for optionally removing comments and script
-  my @comment;
-  my @script;
-  if ($REMOVE_SCRIPT) {
-    $$ref =~ s|(<script\b.+?</script>)|push(@script, $1);$MARKER_SCRIPT|egi;
-  }
-  if ($REMOVE_COMMENT) {
-    $$ref =~ s|(<!--.*?-->)|push(@comment, $1);$MARKER_COMMENT|eg;
-  }
-
-  ### if there is a target - focus in on it
-  ### possible bug here - name won't be found if
-  ### there is nested html inside the form tag that comes before
-  ### the name field - if no close form tag - don't swap in anything
-  if ($target) {
-    local $TEMP_TARGET = $target;
-    $$ref =~ s{(<form            # open form
-                [^>]+            # some space
-                \bname=([\"\']?) # the name tag
-                $target          # with the correct name (allows for regex)
-                \2               # closing quote
-                .+?              # as much as there is
-                (?=</form>))     # then end
-              }{
-                local $REMOVE_SCRIPT  = undef;
-                local $REMOVE_COMMENT = undef;
-                &form_fill($1, $form, undef, $fill_password, $ignore);
-              }sigex;
+    my $text          = shift;
+    my $ref           = ref($text) ? $text : \$text;
+    my $form          = shift;
+    my $target        = shift;
+    my $fill_password = shift;
+    my $ignore        = shift || {};
+
+    fill({
+        text          => $ref,
+        form          => $form,
+        target        => $target,
+        fill_password => $fill_password,
+        ignore_fields => $ignore,
+    });
 
-    ### put scripts and comments back and return
-    $$ref =~ s/$MARKER_COMMENT/shift(@comment)/eg if $#comment != -1;
-    $$ref =~ s/$MARKER_SCRIPT/ shift(@script) /eg if $#script  != -1;
     return ref($text) ? 1 : $$ref;
-  }
+}
 
-  ### build a sub to get a value
-  my %indexes = (); # store indexes for multivalued elements
-  my $get_form_value = sub {
-    my $key = shift;
-    my $all = $_[0] && $_[0] eq 'all';
-    if (! defined $key || ! length $key) {
-      return $all ? [] : undef;
-    }
+sub fill {
+    my $args          = shift;
+    my $ref           = $args->{'text'};
+    my $form          = $args->{'form'};
+    my $target        = $args->{'target'};
+    my $ignore        = $args->{'ignore_fields'};
+    my $fill_password = $args->{'fill_password'};
+
+    my $forms = UNIVERSAL::isa($form, 'ARRAY') ? $form : [$form];
+    $ignore = {map {$_ => 1} @$ignore} if UNIVERSAL::isa($ignore, 'ARRAY');
+    $fill_password = 1 if ! defined $fill_password;
 
-    my $val;
-    my $meth;
-    foreach my $form (@$forms) {
-      next if ! ref $form;
-      if (UNIVERSAL::isa($form, 'HASH') && defined $form->{$key}) {
-        $val = $form->{$key};
-        last;
-      } elsif ($meth = UNIVERSAL::can($form, $OBJECT_METHOD)) {
-        $val = $form->$meth($key);
-        last if defined $val;
-      } elsif (UNIVERSAL::isa($form, 'CODE')) {
-        $val = &{ $form }($key, $TEMP_TARGET);
-        last if defined $val;
-      }
-    }
-    if (! defined $val) {
-      return $all ? [] : undef;
-    }
 
-    ### fix up the value some
-    if (UNIVERSAL::isa($val, 'CODE')) {
-      $val = &{ $val }($key, $TEMP_TARGET);
+    ### allow for optionally removing comments and script
+    my @comment;
+    my @script;
+    if (defined($args->{'remove_script'}) ? $args->{'remove_script'} : $REMOVE_SCRIPT) {
+        $$ref =~ s|(<script\b.+?</script>)|push(@script, $1);$MARKER_SCRIPT|egi;
     }
-    if (UNIVERSAL::isa($val, 'ARRAY')) {
-      $val = [@$val]; # copy the values
-    } elsif (ref $val) {
-      # die "Value for $key is not an array or a scalar";
-      $val = "$val";  # stringify anything else
+    if (defined($args->{'remove_comment'}) ? $args->{'remove_comment'} : $REMOVE_COMMENT) {
+        $$ref =~ s|(<!--.*?-->)|push(@comment, $1);$MARKER_COMMENT|eg;
     }
 
-    ### html escape them all
-    &html_escape(\$_) foreach (ref($val) ? @$val : $val);
-
-    ### allow for returning all elements
-    ### or one at a time
-    if ($all) {
-      return ref($val) ? $val : [$val];
-    } elsif (ref($val)) {
-      $indexes{$key} ||= 0;
-      my $ret = $val->[$indexes{$key}] || '';
-      $indexes{$key} ++; # don't wrap - if we run out of values - we're done
-      return $ret;
-    } else {
-      return $val;
+    ### if there is a target - focus in on it
+    ### possible bug here - name won't be found if
+    ### there is nested html inside the form tag that comes before
+    ### the name field - if no close form tag - don't swap in anything
+    if ($target) {
+        local $_TEMP_TARGET = $target;
+        $$ref =~ s{(<form            # open form
+                    [^>]+            # some space
+                    \bname=([\"\']?) # the name tag
+                    $target          # with the correct name (allows for regex)
+                    \2               # closing quote
+                    .+?              # as much as there is
+                    (?=</form>))     # then end
+                   }{
+                       my $str = $1;
+                       local $args->{'text'} = \$str;
+                       local $args->{'remove_script'}  = 0;
+                       local $args->{'remove_comment'} = 0;
+                       local $args->{'target'}         = undef;
+                       fill($args);
+                       $str; # return of the s///;
+                   }sigex;
+
+        ### put scripts and comments back and return
+        $$ref =~ s/$MARKER_COMMENT/shift(@comment)/eg if $#comment != -1;
+        $$ref =~ s/$MARKER_SCRIPT/ shift(@script) /eg if $#script  != -1;
+        return 1;
     }
-  };
-
-
-  ###--------------------------------------------------------------###
-
-  ### First pass
-  ### swap <input > form elements if they have a name
-  $$ref =~ s{
-    (<input \s (?: ([\"\'])(?:|.*?[^\\])\2 | [^>] )* >) # nested html ok
-    }{
-      ### get the type and name - intentionally exlude names with nested "'
-      my $tag   = $1;
-      my $type  = uc(&get_tagval_by_key(\$tag, 'type') || '');
-      my $name  = &get_tagval_by_key(\$tag, 'name');
-
-      if ($name && ! $ignore->{$name}) {
-        if (! $type
-            || $type eq 'HIDDEN'
-            || $type eq 'TEXT'
-            || $type eq 'FILE'
-            || ($type eq 'PASSWORD' && $fill_password)) {
-          
-          my $value = &$get_form_value($name, 'next');
-          if (defined $value) {
-            &swap_tagval_by_key(\$tag, 'value', $value);
-          } elsif (! defined &get_tagval_by_key(\$tag, 'value')) {
-            &swap_tagval_by_key(\$tag, 'value', '');
-          }
-
-        } elsif ($type eq 'CHECKBOX'
-                 || $type eq 'RADIO') {
-          my $values = &$get_form_value($name, 'all');          
-          if (@$values) {
-            $tag =~ s{\s+\bCHECKED\b(?:=([\"\']?)checked\1)?(?=\s|>|/>)}{}ig;
-            
-            if ($type eq 'CHECKBOX' && @$values == 1 && $values->[0] eq 'on') {
-              $tag =~ s|(/?>\s*)$| checked="checked"$1|;
-            } else {
-              my $fvalue = &get_tagval_by_key(\$tag, 'value');
-              if (defined $fvalue) {
-                foreach (@$values) {
-                  next if $_ ne $fvalue;
-                  $tag =~ s|(\s*/?>\s*)$| checked="checked"$1|;
-                  last;
-                }
-              }
+
+    ### build a sub to get a value from the passed forms on a request basis
+    my %indexes = (); # store indexes for multivalued elements
+    my $get_form_value = sub {
+        my $key = shift;
+        my $all = $_[0] && $_[0] eq 'all';
+        if (! defined $key || ! length $key) {
+            return $all ? [] : undef;
+        }
+
+        my $val;
+        my $meth;
+        foreach my $form (@$forms) {
+            next if ! ref $form;
+            if (UNIVERSAL::isa($form, 'HASH') && defined $form->{$key}) {
+                $val = $form->{$key};
+                last;
+            } elsif ($meth = UNIVERSAL::can($form, $args->{'object_method'} || $OBJECT_METHOD)) {
+                $val = $form->$meth($key);
+                last if defined $val;
+            } elsif (UNIVERSAL::isa($form, 'CODE')) {
+                $val = $form->($key, $_TEMP_TARGET);
+                last if defined $val;
             }
-          }
         }
-      }
-      $tag; # return of swap
-    }sigex;
-
-
-  ### Second pass
-  ### swap select boxes (must be done in such a way as to allow no closing tag)
-  my @start = ();
-  my @close = ();
-  push @start, pos($$ref) - length($1) while $$ref =~ m|(<\s*select\b)|ig;
-  push @close, pos($$ref) - length($1) while $$ref =~ m|(</\s*select\b)|ig;
-  for (my $i = 0; $i <= $#start; $i ++) {
-    while (defined($close[$i]) && $close[$i] < $start[$i]) {
-      splice (@close,$i,1,());
-    }
-    if ($i == $#start) {
-      $close[$i] = length($$ref) if ! defined $close[$i]; # set to end of string if no closing
-    } elsif (! defined($close[$i]) || $close[$i] > $start[$i + 1]) {
-      $close[$i] = $start[$i + 1]; # set to start of next select if no closing or > next select
-    }
-  }
-  for (my $i = $#start; $i >= 0; $i --) {
-    my $opts = substr($$ref, $start[$i], $close[$i] - $start[$i]);
-    $opts =~ s{
-      (<select \s                                 # opening
-       (?: "" | '' | ([\"\']).*?[^\\]\2 | [^>] )* # nested html ok
-       >)                                         # end of tag
-      }{}sxi || next;
-    next if ! $opts;
-    my $tag    = $1;
-    my $name   = &get_tagval_by_key(\$tag, 'name');
-    my $values = $ignore->{$name} ? [] : &$get_form_value($name, 'all');
-    if ($#$values != -1) {
-      my $n = $opts =~ s{
-        (<option[^>]*>)           # opening tag - no embedded > allowed
-          (.*?)                   # the text value
-          (?=<option|$|</option>) # the next tag
+        if (! defined $val) {
+            return $all ? [] : undef;
+        }
+
+        ### fix up the value some
+        if (UNIVERSAL::isa($val, 'CODE')) {
+            $val = $val->($key, $_TEMP_TARGET);
+        }
+        if (UNIVERSAL::isa($val, 'ARRAY')) {
+            $val = [@$val]; # copy the values
+        } elsif (ref $val) {
+            # die "Value for $key is not an array or a scalar";
+            $val = "$val";  # stringify anything else
+        }
+
+        ### html escape them all
+        html_escape(\$_) foreach (ref($val) ? @$val : $val);
+
+        ### allow for returning all elements
+        ### or one at a time
+        if ($all) {
+            return ref($val) ? $val : [$val];
+        } elsif (ref($val)) {
+            $indexes{$key} ||= 0;
+            my $ret = $val->[$indexes{$key}];
+            $ret = '' if ! defined $ret;
+            $indexes{$key} ++; # don't wrap - if we run out of values - we're done
+            return $ret;
+        } else {
+            return $val;
+        }
+    };
+
+
+    ###--------------------------------------------------------------###
+
+    ### First pass
+    ### swap <input > form elements if they have a name
+    $$ref =~ s{
+        (<input \s (?: ([\"\'])(?:|.*?[^\\])\2 | [^>] )* >) # nested html ok
         }{
-          my ($tag2, $opt) = ($1, $2);
-          $tag2 =~ s%\s+\bSELECTED\b(?:=([\"\']?)selected\1)?(?=\s|>|/>)%%ig;
-          
-          my $fvalues = &get_tagval_by_key(\$tag2, 'value', 'all');
-          my $fvalue  = @$fvalues ? $fvalues->[0]
-            : $opt =~ /^\s*(.*?)\s*$/ ? $1 : "";
-          foreach (@$values) {
-            next if $_ ne $fvalue;
-            $tag2 =~ s|(\s*/?>\s*)$| selected="selected"$1|;
-            last;
-          }
-          "$tag2$opt"; # return of the swap
+            ### get the type and name - intentionally exlude names with nested "'
+            my $tag   = $1;
+            my $type  = uc(get_tagval_by_key(\$tag, 'type') || '');
+            my $name  = get_tagval_by_key(\$tag, 'name');
+
+            if ($name && ! $ignore->{$name}) {
+                if (! $type
+                    || $type eq 'HIDDEN'
+                    || $type eq 'TEXT'
+                    || $type eq 'FILE'
+                    || ($type eq 'PASSWORD' && $fill_password)) {
+
+                    my $value = $get_form_value->($name, 'next');
+                    if (defined $value) {
+                        swap_tagval_by_key(\$tag, 'value', $value);
+                    } elsif (! defined get_tagval_by_key(\$tag, 'value')) {
+                        swap_tagval_by_key(\$tag, 'value', '');
+                    }
+
+                } elsif ($type eq 'CHECKBOX'
+                         || $type eq 'RADIO') {
+                    my $values = $get_form_value->($name, 'all');
+                    if (@$values) {
+                        $tag =~ s{\s+\bCHECKED\b(?:=([\"\']?)checked\1)?(?=\s|>|/>)}{}ig;
+
+                        my $fvalue = get_tagval_by_key(\$tag, 'value');
+                        $fvalue = 'on' if ! defined $fvalue;
+                        if (defined $fvalue) {
+                            foreach (@$values) {
+                                next if $_ ne $fvalue;
+                                $tag =~ s|(\s*/?>\s*)$| checked="checked"$1|;
+                                last;
+                            }
+                        }
+                    }
+                }
+
+            }
+            $tag; # return of swap
         }sigex;
-      if ($n) {
-        substr($$ref, $start[$i], $close[$i] - $start[$i], "$tag$opts");
-      }
+
+
+    ### Second pass
+    ### swap select boxes (must be done in such a way as to allow no closing tag)
+    my @start = ();
+    my @close = ();
+    push @start, pos($$ref) - length($1) while $$ref =~ m|(<\s*select\b)|ig;
+    push @close, pos($$ref) - length($1) while $$ref =~ m|(</\s*select\b)|ig;
+    for (my $i = 0; $i <= $#start; $i ++) {
+        while (defined($close[$i]) && $close[$i] < $start[$i]) {
+            splice (@close,$i,1,());
+        }
+        if ($i == $#start) {
+            $close[$i] = length($$ref) if ! defined $close[$i]; # set to end of string if no closing
+        } elsif (! defined($close[$i]) || $close[$i] > $start[$i + 1]) {
+            $close[$i] = $start[$i + 1]; # set to start of next select if no closing or > next select
+        }
     }
-  }
-
-
-  ### Third pass
-  ### swap textareas (must be done in such a way as to allow no closing tag)
-  @start = ();
-  @close = ();
-  push @start, pos($$ref) - length($1) while $$ref =~ m|(<\s*textarea\b)|ig;
-  push @close, pos($$ref) - length($1) while $$ref =~ m|(</\s*textarea\b)|ig;
-  for (my $i = 0; $i <= $#start; $i ++) {
-    while (defined($close[$i]) && $close[$i] < $start[$i]) {
-      splice (@close,$i,1,());
+    for (my $i = $#start; $i >= 0; $i --) {
+        my $opts = substr($$ref, $start[$i], $close[$i] - $start[$i]);
+        $opts =~ s{
+            (<select \s                                 # opening
+             (?: "" | '' | ([\"\']).*?[^\\]\2 | [^>] )* # nested html ok
+             >)                                         # end of tag
+            }{}sxi || next;
+        next if ! $opts;
+        my $tag    = $1;
+        my $name   = get_tagval_by_key(\$tag, 'name');
+        my $values = $ignore->{$name} ? [] : $get_form_value->($name, 'all');
+        if ($#$values != -1) {
+            my $n = $opts =~ s{
+                (<option[^>]*>)           # opening tag - no embedded > allowed
+                    (.*?)                   # the text value
+                    (?=<option|$|</option>) # the next tag
+                }{
+                    my ($tag2, $opt) = ($1, $2);
+                    $tag2 =~ s%\s+\bSELECTED\b(?:=([\"\']?)selected\1)?(?=\s|>|/>)%%ig;
+
+                    my $fvalues = get_tagval_by_key(\$tag2, 'value', 'all');
+                    my $fvalue  = @$fvalues ? $fvalues->[0]
+                        : $opt =~ /^\s*(.*?)\s*$/ ? $1 : "";
+                    foreach (@$values) {
+                        next if $_ ne $fvalue;
+                        $tag2 =~ s|(\s*/?>\s*)$| selected="selected"$1|;
+                        last;
+                    }
+                    "$tag2$opt"; # return of the swap
+                }sigex;
+            if ($n) {
+                substr($$ref, $start[$i], $close[$i] - $start[$i], "$tag$opts");
+            }
+        }
     }
-    if ($i == $#start) {
-      $close[$i] = length($$ref) if ! defined $close[$i]; # set to end of string if no closing
-    } elsif (! defined($close[$i]) || $close[$i] > $start[$i + 1]) {
-      $close[$i] = $start[$i + 1]; # set to start of next select if no closing or > next select
+
+
+    ### Third pass
+    ### swap textareas (must be done in such a way as to allow no closing tag)
+    @start = ();
+    @close = ();
+    push @start, pos($$ref) - length($1) while $$ref =~ m|(<\s*textarea\b)|ig;
+    push @close, pos($$ref) - length($1) while $$ref =~ m|(</\s*textarea\b)|ig;
+    for (my $i = 0; $i <= $#start; $i ++) {
+        while (defined($close[$i]) && $close[$i] < $start[$i]) {
+            splice (@close,$i,1,()); # get rid of extra closes
+        }
+        if ($i == $#start) {
+            $close[$i] = length($$ref) if ! defined $close[$i]; # set to end of string if no closing
+        } elsif (! defined($close[$i]) || $close[$i] > $start[$i + 1]) {
+            splice(@close, $i, 0, $start[$i + 1]); # set to start of next select if no closing or > next select
+        }
+    }
+    my $offset = 0;
+    for (my $i = 0; $i <= $#start; $i ++) {
+        my $oldval = substr($$ref, $start[$i] + $offset, $close[$i] - $start[$i]);
+        $oldval =~ s{
+            (<textarea \s                               # opening
+             (?: "" | '' | ([\"\']).*?[^\\]\2 | [^>] )* # nested html ok
+             >)                                         # end of tag
+            }{}sxi || next;
+        my $tag  = $1;
+        my $name = get_tagval_by_key(\$tag, 'name');
+        if ($name && ! $ignore->{$name}) {
+            my $value = $get_form_value->($name, 'next');
+            next if ! defined $value;
+            substr($$ref, $start[$i] + $offset, $close[$i] - $start[$i], "$tag$value");
+            $offset += length($value) - length($oldval);
+        }
     }
-  }
-  for (my $i = $#start; $i >= 0; $i --) {
-    my $oldval = substr($$ref, $start[$i], $close[$i] - $start[$i]);
-    $oldval =~ s{
-      (<textarea \s                               # opening
-       (?: "" | '' | ([\"\']).*?[^\\]\2 | [^>] )* # nested html ok
-       >)                                         # end of tag
-      }{}sxi || next;
-    my $tag   = $1;
-    my $name  = &get_tagval_by_key(\$tag, 'name');
-    my $value = $ignore->{$name} ? [] : &$get_form_value($name, 'next');
-    next if ! defined $value;
-    substr($$ref, $start[$i], $close[$i] - $start[$i], "$tag$value");
-  }
-
-  ### put scripts and comments back and return
-  $$ref =~ s/$MARKER_COMMENT/shift(@comment)/eg if $#comment != -1;
-  $$ref =~ s/$MARKER_SCRIPT/ shift(@script) /eg if $#script  != -1;
-  return ref($text) ? 1 : $$ref;
+
+    ### put scripts and comments back and return
+    $$ref =~ s/$MARKER_COMMENT/shift(@comment)/eg if $#comment != -1;
+    $$ref =~ s/$MARKER_SCRIPT/ shift(@script) /eg if $#script  != -1;
+    return 1;
 }
 
 
 ### yet another html escaper
 ### allow pass by value or by reference (reference is modified inplace)
 sub html_escape {
-  my $str = shift;
-  return $str if ! $str;
-  my $ref = ref($str) ? $str : \$str;
+    my $str = shift;
+    return $str if ! $str;
+    my $ref = ref($str) ? $str : \$str;
 
-  $$ref =~ s/&/&amp;/g;
-  $$ref =~ s/</&lt;/g;
-  $$ref =~ s/>/&gt;/g;
-  $$ref =~ s/\"/&quot;/g;
+    $$ref =~ s/&/&amp;/g;
+    $$ref =~ s/</&lt;/g;
+    $$ref =~ s/>/&gt;/g;
+    $$ref =~ s/\"/&quot;/g;
 
-  return ref($str) ? 1 : $$ref;
+    return ref($str) ? 1 : $$ref;
 }
 
 ### get a named value for key="value" pairs
-### usage: my $val     = &get_tagval_by_key(\$tag, $key);
-### usage: my $valsref = &get_tagval_by_key(\$tag, $key, 'all');
+### usage: my $val     = get_tagval_by_key(\$tag, $key);
+### usage: my $valsref = get_tagval_by_key(\$tag, $key, 'all');
 sub get_tagval_by_key {
-  my $tag = shift;
-  my $ref = ref($tag) ? $tag : \$tag;
-  my $key = lc(shift);
-  my $all = $_[0] && $_[0] eq 'all';
-  my @all = ();
-  pos($$ref) = 0; # fix for regex below not resetting and forcing order on key value pairs
-
-  ### loop looking for tag pairs
-  while ($$ref =~ m{
-    (?<![\w\.\-])                  # 0 - not proceded by letter or .
-      ([\w\.\-]+)                  # 1 - the key
-      \s*=                         # equals
-      (?: \s*([\"\'])(|.*?[^\\])\2 # 2 - a quote, 3 - the quoted
-       |  ([^\s/]*? (?=\s|>|/>))   # 4 - a non-quoted string
-       )
-    }sigx) {
-    next if lc($1) ne $key;
-    my ($val,$quot) = ($2) ? ($3,$2) : ($4,undef);
-    $val =~ s/\\$quot/$quot/ if $quot;
-    return $val if ! $all;
-    push @all, $val;
-  }
-  return undef if ! $all;
-  return \@all;
+    my $tag = shift;
+    my $ref = ref($tag) ? $tag : \$tag;
+    my $key = lc(shift);
+    my $all = $_[0] && $_[0] eq 'all';
+    my @all = ();
+    pos($$ref) = 0; # fix for regex below not resetting and forcing order on key value pairs
+
+    ### loop looking for tag pairs
+    while ($$ref =~ m{
+        (?<![\w\.\-])                  # 0 - not proceded by letter or .
+            ([\w\.\-]+)                  # 1 - the key
+            \s*=                         # equals
+            (?: \s*([\"\'])(|.*?[^\\])\2 # 2 - a quote, 3 - the quoted
+             |  ([^\s/]*? (?=\s|>|/>))   # 4 - a non-quoted string
+             )
+        }sigx) {
+        next if lc($1) ne $key;
+        my ($val,$quot) = ($2) ? ($3,$2) : ($4,undef);
+        $val =~ s/\\$quot/$quot/ if $quot;
+        return $val if ! $all;
+        push @all, $val;
+    }
+    return undef if ! $all;
+    return \@all;
 }
 
 ### swap out values for key="value" pairs
 ### usage: my $count  = &swap_tagval_by_key(\$tag, $key, $val);
 ### usage: my $newtag = &swap_tagval_by_key($tag, $key, $val);
 sub swap_tagval_by_key {
-  my $tag = shift;
-  my $ref = ref($tag) ? $tag : \$tag;
-  my $key = lc(shift);
-  my $val = shift;
-  my $n   = 0;
-
-  ### swap a key/val pair at time
-  $$ref =~ s{(^\s*<\s*\w+\s+ | \G\s+)         # 1 - open tag or previous position
-               ( ([\w\-\.]+)                  # 2 - group, 3 - the key
-                 (\s*=)                       # 4 - equals
-                  (?: \s* ([\"\']) (?:|.*?[^\\]) \5 # 5 - the quote mark, the quoted
-                   |  [^\s/]*? (?=\s|>|/>)    # a non-quoted string (may be zero length)
-                  )
-                | ([^\s/]+?) (?=\s|>|/>)      # 6 - a non keyvalue chunk (CHECKED)
-               )
-             }{
-               if (defined($3) && lc($3) eq $key) { # has matching key value pair
-                 if (! $n ++) {  # only put value back on first match
-                   "$1$3$4\"$val\""; # always double quote
-                 } else {
-                   $1; # second match
-                 }
-               } elsif (defined($6) && lc($6) eq $key) { # has matching key
-                 if (! $n ++) {  # only put value back on first match
-                   "$1$6=\"$val\"";
-                 } else {
-                   $1; # second match
-                 }
-               } else {
-                 "$1$2"; # non-keyval
-               }
-             }sigex;
-
-  ### append value on if none were swapped
-  if (! $n) {
-    $$ref =~ s|(\s*/?>\s*)$| value="$val"$1|;
-    $n = -1;
-  }
-
-  return ref($tag) ? $n : $$ref;
+    my $tag = shift;
+    my $ref = ref($tag) ? $tag : \$tag;
+    my $key = lc(shift);
+    my $val = shift;
+    my $n   = 0;
+
+    ### swap a key/val pair at time
+    $$ref =~ s{(^\s*<\s*\w+\s+ | \G\s+)         # 1 - open tag or previous position
+                   ( ([\w\-\.]+)                  # 2 - group, 3 - the key
+                     (\s*=)                       # 4 - equals
+                     (?: \s* ([\"\']) (?:|.*?[^\\]) \5 # 5 - the quote mark, the quoted
+                      |  [^\s/]*? (?=\s|>|/>)    # a non-quoted string (may be zero length)
+                      )
+                     | ([^\s/]+?) (?=\s|>|/>)      # 6 - a non keyvalue chunk (CHECKED)
+                     )
+               }{
+                   if (defined($3) && lc($3) eq $key) { # has matching key value pair
+                       if (! $n ++) {  # only put value back on first match
+                           "$1$3$4\"$val\""; # always double quote
+                       } else {
+                           $1; # second match
+                       }
+                   } elsif (defined($6) && lc($6) eq $key) { # has matching key
+                       if (! $n ++) {  # only put value back on first match
+                           "$1$6=\"$val\"";
+                       } else {
+                           $1; # second match
+                       }
+                   } else {
+                       "$1$2"; # non-keyval
+                   }
+               }sigex;
+
+    ### append value on if none were swapped
+    if (! $n) {
+        $$ref =~ s|(\s*/?>\s*)$| value="$val"$1|;
+        $n = -1;
+    }
+
+    return ref($tag) ? $n : $$ref;
 }
 
 1;
@@ -388,58 +420,376 @@ __END__
 
 ###----------------------------------------------------------------###
 
-=head1 NAME
+=head1 SYNOPSIS
 
-CGI::Ex::Fill - Yet another form filler
+    use CGI::Ex::Fill qw(form_fill fill);
 
-=head1 SYNOPSIS
+    my $text = my_own_template_from_somewhere();
 
-  use CGI::Ex::Fill qw(form_fill);
+    my $form = CGI->new;
+    # OR
+    # my $form = {key => 'value'}
+    # OR
+    # my $form = [CGI->new, CGI->new, {key1 => 'val1'}, CGI->new];
 
-  my $text = my_own_template_from_somewhere();
 
-  my $form = CGI->new;
-  # OR
-  # my $form = {key => 'value'}
-  # OR 
-  # my $form = [CGI->new, CGI->new, {key1 => 'val1'}, CGI->new];
+    form_fill(\$text, $form); # modifies $text
 
+    # OR
+    # my $copy = form_fill($text, $form); # copies $text
 
-  form_fill(\$text, $form); # modifies $text
-  # OR
-  # my $copy = form_fill($text, $form); # copies $text
+    # OR
+    fill({
+        text => \$text,
+        form => $form,
+    });
 
 
-  ALSO
+    # ALSO
 
-  my $formname = 'formname';     # table to parse (undef = anytable)
-  my $fp = 0;                    # fill_passwords ? default is true
-  my $ignore = ['key1', 'key2']; # OR {key1 => 1, key2 => 1};
+    my $formname = 'formname';     # form to parse (undef = anytable)
+    my $fp = 0;                    # fill_passwords ? default is true
+    my $ignore = ['key1', 'key2']; # OR {key1 => 1, key2 => 1};
 
-  form_fill(\$text, $form, $formname, $fp, $ignore);
+    form_fill(\$text, $form, $formname, $fp, $ignore);
 
-  ALSO
+    # OR
+    fill({
+        text          => \$text,
+        form          => $form,
+        target        => 'my_formname',
+        fill_password => $fp,
+        ignore_fields => $ignore,
+    });
 
-  ### delay getting the value until we find an element that needs it
-  my $form = {key => sub {my $key = shift; # get and return value}};
+    # ALSO
+
+    ### delay getting the value until we find an element that needs it
+    my $form = {key => sub {my $key = shift; # get and return value}};
 
 
 =head1 DESCRIPTION
 
-form_fill is directly comparable to HTML::FillInForm.  It will pass the
-same suite of tests (actually - it is a little bit kinder on the parse as
-it won't change case, reorder your attributes, or miscellaneous spaces).
-
-HTML::FillInForm both benefits and suffers from being based on
-HTML::Parser. It is good for standards and poor for performance.  Testing
-the form_fill module against HTML::FillInForm gave some surprising
-results.  On tiny forms (< 1 k) form_fill was ~ 17% faster than FillInForm.
-If the html document incorporated very many entities at all, the
-performace of FillInForm goes down (and down).  However, if you are only
-filling in one form every so often, then it shouldn't matter - but form_fill
-will be nicer on the tags and won't balk at ugly html.
-See the benchmarks in the t/samples directory for more information (ALL
-BENCHMARKS SHOULD BE TAKEN WITH A GRAIN OF SALT).
+form_fill is directly comparable to HTML::FillInForm.  It will pass
+the same suite of tests (actually - it is a little bit kinder on the
+parse as it won't change case, reorder your attributes, or alter
+miscellaneous spaces and it won't require the HTML to be well formed).
+
+HTML::FillInForm is based upon HTML::Parser while CGI::Ex::Fill is
+purely regex driven.  The performance of CGI::Ex::Fill will be better
+on HTML with many markup tags because HTML::Parser will parse each tag
+while CGI::Ex::Fill will search only for those tags it knows how to
+handle.  And CGI::Ex::Fill generally won't break on malformed html.
+
+On tiny forms (< 1 k) form_fill was ~ 13% slower than FillInForm.  If
+the html document incorporated very many entities at all, the
+performance of FillInForm goes down (adding 360 <br> tags pushed
+form_fill to ~ 350% faster).  However, if you are only filling in one
+form every so often, then it shouldn't matter which you use - but
+form_fill will be nicer on the tags and won't balk at ugly html and
+will decrease performance only at a slow rate as the size of the html
+increases.  See the benchmarks in the t/samples/bench_cgix_hfif.pl
+file for more information (ALL BENCHMARKS SHOULD BE TAKEN WITH A GRAIN
+OF SALT).
+
+There are two functions, fill and form_fill.  The function fill takes
+a hashref of named arguments.  The function form_fill takes a list
+of positional parameters.
+
+=head1 ARGUMENTS TO form_fill
+
+The following are the arguments to the main function C<fill>.
+
+=over 4
+
+=item text
+
+A reference to an html string that includes one or more forms.
+
+=item form
+
+A form hash, CGI object, or an array of hashrefs and objects.
+
+=item target
+
+The name of the form to swap.  Default is undef which means
+to swap all form entities in all forms.
+
+=item fill_password
+
+Default true.  If set to false, fields of type password will
+not be refilled.
+
+=item ignore_fields
+
+Hashref of fields to be ignored from swapping.
+
+=item remove_script
+
+Defaults to the package global $REMOVE_SCRIPT which defaults to true.
+Removes anything in <script></script> tags which often cause problems for
+parsers.
+
+=item remove_comment
+
+Defaults to the package global $REMOVE_COMMENT which defaults to true.
+Removes anything in <!-- --> tags which can sometimes cause problems for
+parsers.
+
+=item object_method
+
+The method to call on objects passed to the form argument.  Default value
+is the package global $OBJECT_METHOD which defaults to 'param'.  If a
+CGI object is passed, it would call param on that object passing
+the desired keyname as an argument.
+
+=back
+
+=head1 ARGUMENTS TO form_fill
+
+The following are the arguments to the legacy function C<form_fill>.
+
+=over 4
+
+=item C<\$html>
+
+A reference to an html string that includes one or more forms or form
+entities.
+
+=item C<\%FORM>
+
+A form hash, or CGI query object, or an arrayref of multiple hash refs
+and/or CGI query objects that will supply values for the form.
+
+=item C<$form_name>
+
+The name of the form to fill in values for.  The default is undef
+which indicates that all forms are to be filled in.
+
+=item C<$swap_pass>
+
+Default true.  Indicates that C<<lt>input type="password"<gt>> fields
+are to be swapped as well.  Set to false to disable this behavior.
+
+=item C<\%IGNORE_FIELDS> OR C<\@IGNORE_FIELDS>
+
+A hash ref of key names or an array ref of key names that will be
+ignored during the fill in of the form.
+
+=back
+
+=head1 BEHAVIOR
+
+fill and form_fill will attempt to DWYM when filling in values.  The following behaviors
+are used on the following types of form elements.
+
+=over 4
+
+=item C<E<lt>input type="text"E<gt>>
+
+The following rules are used when matching this type:
+
+   1) Get the value from the form that matches the input's "name".
+   2) If the value is defined - it adds or replaces the existing value.
+   3) If the value is not defined and the existing value is not defined,
+      a value of "" is added.
+
+For example:
+
+   my $form = {foo => "FOO", bar => "BAR", baz => "BAZ"};
+
+   my $html = '
+       <input type=text name=foo>
+       <input type=text name=foo>
+       <input type=text name=bar value="">
+       <input type=text name=baz value="Something else">
+       <input type=text name=hem value="Another thing">
+       <input type=text name=haw>
+   ';
+
+   form_fill(\$html, $form);
+
+   $html eq   '
+       <input type=text name=foo value="FOO">
+       <input type=text name=foo value="FOO">
+       <input type=text name=bar value="BAR">
+       <input type=text name=baz value="BAZ">
+       <input type=text name=hem value="Another thing">
+       <input type=text name=haw value="">
+   ';
+
+
+If the value returned from the form is an array ref, the values of the array ref
+will be sequentially used for each input found by that name until the values
+run out.  If the value is not an array ref - it will be used to fill in any values
+by that name.  For example:
+
+   $form = {foo => ['aaaa', 'bbbb', 'cccc']};
+
+   $html = '
+       <input type=text name=foo>
+       <input type=text name=foo>
+       <input type=text name=foo>
+       <input type=text name=foo>
+       <input type=text name=foo>
+   ';
+
+   form_fill(\$html, $form);
+
+   $html eq  '
+       <input type=text name=foo value="aaaa">
+       <input type=text name=foo value="bbbb">
+       <input type=text name=foo value="cccc">
+       <input type=text name=foo value="">
+       <input type=text name=foo value="">
+   ';
+
+=item C<E<lt>input type="hidden"E<gt>>
+
+Same as C<E<lt>input type="text"E<gt>>.
+
+=item C<E<lt>input type="password"E<gt>>
+
+Same as C<E<lt>input type="text"E<gt>>.
+
+=item C<E<lt>input type="file"E<gt>>
+
+Same as C<E<lt>input type="text"E<gt>>.  (Note - this is subject
+to browser support for pre-population)
+
+=item C<E<lt>input type="checkbox"E<gt>>
+
+As each checkbox is found the following rules are applied:
+
+    1) Get the values from the form (do nothing if no values found)
+    2) Remove any existing "checked=checked" or "checked" markup from the tag.
+    3) Compare the "value" field to the values and mark with checked="checked"
+    if there is a match.
+
+If no "value" field is found in the html, a default value of "on" will be used (which is
+what most browsers will send as the default value for checked boxes without
+"value" fields).
+
+   $form = {foo => 'FOO', bar => ['aaaa', 'bbbb', 'cccc'], baz => 'on'};
+
+   $html = '
+       <input type=checkbox name=foo value="123">
+       <input type=checkbox name=foo value="FOO">
+       <input type=checkbox name=bar value="aaaa">
+       <input type=checkbox name=bar value="cccc">
+       <input type=checkbox name=bar value="dddd" checked="checked">
+       <input type=checkbox name=baz>
+   ';
+
+   form_fill(\$html, $form);
+
+   $html eq  '
+       <input type=checkbox name=foo value="123">
+       <input type=checkbox name=foo value="FOO" checked="checked">
+       <input type=checkbox name=bar value="aaaa" checked="checked">
+       <input type=checkbox name=bar value="cccc" checked="checked">
+       <input type=checkbox name=bar value="dddd">
+       <input type=checkbox name=baz checked="checked">
+   ';
+
+
+=item C<E<lt>input type="radio"E<gt>>
+
+Same as C<E<lt>input type="checkbox"E<gt>>.
+
+=item C<E<lt>selectE<gt>>
+
+As each select box is found the following rules are applied (these rules are
+applied regardless of if the box is a select-one or a select-multi - if multiple
+values are selected on a select-one it is up to the browser to choose which one
+to highlight):
+
+    1) Get the values from the form (do nothing if no values found)
+    2) Remove any existing "selected=selected" or "selected" markup from the tag.
+    3) Compare the "value" field to the values and mark with selected="selected"
+    if there is a match.
+    4) If there is no "value" field - use the text in between the "option" tags.
+
+    (Note: There does not need to be a closing "select" tag or closing "option" tag)
+
+
+   $form = {foo => 'FOO', bar => ['aaaa', 'bbbb', 'cccc']};
+
+   $html = '
+       <select name=foo><option>FOO<option>123<br>
+
+       <select name=bar>
+         <option>aaaa</option>
+         <option value="cccc">cccc</option>
+         <option value="dddd" selected="selected">dddd</option>
+       </select>
+   ';
+
+   form_fill(\$html, $form);
+
+   ok(
+   $html eq  '
+       <select name=foo><option selected="selected">FOO<option>123<br>
+
+       <select name=bar>
+         <option selected="selected">aaaa</option>
+         <option value="cccc" selected="selected">cccc</option>
+         <option value="dddd">dddd</option>
+       </select>
+   ', "Perldoc example 4 passed");
+
+
+=item C<E<lt>textareaE<gt>>
+
+The rules for swapping textarea are as follows:
+
+   1) Get the value from the form that matches the textarea's "name".
+   2) If the value is defined - it adds or replaces the existing value.
+   3) If the value is not defined, the text area is left alone.
+
+   (Note - there does not need to be a closing textarea tag.  In the case of
+    a missing close textarea tag, the contents of the text area will be
+    assumed to be the start of the next textarea of the end of the document -
+    which ever comes sooner)
+
+If the form returned an array ref of values, then these values will be
+used sequentially each time a textarea by that name is found.  If a single value
+(not array ref) is found, that value will be used for each textarea by that name.
+
+For example.
+
+   $form = {foo => 'FOO', bar => ['aaaa', 'bbbb']};
+
+   $html = '
+       <textarea name=foo></textarea>
+       <textarea name=foo></textarea>
+
+       <textarea name=bar>
+       <textarea name=bar></textarea><br>
+       <textarea name=bar>dddd</textarea><br>
+       <textarea name=bar><br><br>
+   ';
+
+   form_fill(\$html, $form);
+
+   $html eq  '
+       <textarea name=foo>FOO</textarea>
+       <textarea name=foo>FOO</textarea>
+
+       <textarea name=bar>aaaa<textarea name=bar>bbbb</textarea><br>
+       <textarea name=bar></textarea><br>
+       <textarea name=bar>';
+
+=item C<E<lt>input type="submit"E<gt>>
+
+Does nothing.  The value for submit should typically be set by the
+templating system or application system.
+
+=item C<E<lt>input type="button"E<gt>>
+
+Same as submit.
+
+=back
 
 =head1 HTML COMMENT / JAVASCRIPT
 
@@ -447,19 +797,47 @@ Because there are too many problems that could occur with html
 comments and javascript, form_fill temporarily removes them during the
 fill.  You may disable this behavior by setting $REMOVE_COMMENT and
 $REMOVE_SCRIPT to 0 before calling form_fill.  The main reason for
-doing this would be if you wanted to have form elments inside the
+doing this would be if you wanted to have form elements inside the
 javascript and comments get filled.  Disabling the removal only
 results in a speed increase of 5%. The function uses \0COMMENT\0 and
-\0SCRIPT\0 as placeholders so i'd avoid these in your text (Actually
-they may be reset to whatever you'd like via $MARKER_COMMENT and
-$MARKER_SCRIPT).
+\0SCRIPT\0 as placeholders so it would be good to avoid these in your
+text (Actually they may be reset to whatever you'd like via
+$MARKER_COMMENT and $MARKER_SCRIPT).
 
-=head1 AUTHOR
+=head1 UTILITY FUNCTIONS
 
-Paul Seamons
+=over 4
+
+=item C<html_escape>
+
+Very minimal entity escaper for filled in values.
+
+    my $escaped = html_escape($unescaped);
+
+    html_escape(\$text_to_escape);
+
+=item C<get_tagval_by_key>
+
+Get a named value for from an html tag (key="value" pairs).
+
+    my $val     = get_tagval_by_key(\$tag, $key);
+    my $valsref = get_tagval_by_key(\$tag, $key, 'all'); # get all values
+
+=item C<swap_tagval_by_key>
+
+Swap out values in an html tag (key="value" pairs).
+
+    my $count  = swap_tagval_by_key(\$tag, $key, $val); # modify ref
+    my $newtag = swap_tagval_by_key($tag, $key, $val);  # copies tag
+
+=back
 
 =head1 LICENSE
 
 This module may distributed under the same terms as Perl itself.
 
+=head1 AUTHOR
+
+Paul Seamons
+
 =cut
This page took 0.042165 seconds and 4 git commands to generate.