djbryson has asked for the wisdom of the Perl Monks concerning the following question:

I need some help understanding this code: a subroutine reads in an HTML template and does this switch for checkboxes
s|(<input type=checkbox)(.*)( name=)(\w+)|$1$2$3$4 $check{$4}|ig;

Then:
foreach ($query->param) { $in{$_} = $query->param($_); if ($_=~/^c_/) { $check{$_}="CHECKED" print OUT "$in{$_}\t"; }
My problem is that i'm writing everything to a tab delimited file (file.xls). When the checkbox isn't checked it's blank and doesn't write anything to the file, but I want it to write something like "no data" so that everything lines up. Anyway, i need to understand the above code. Like why is $check{$_} being set to "CHECKED" everytime? Oh, checkbox names must be c_xxxxx

Replies are listed 'Best First'.
Re: checkbox help
by Herkum (Parson) on Mar 05, 2007 at 18:21 UTC

    When you are working with checkboxes, you should already know what boxes are going to to appear on the form.

    It is easy for a someone to slip something onto the web page manually and typos can also be an issue.

    On this case, be explicit, know exactly which entries are supposed to be on the form rather than loop through doing a regular expression on the input names.

      it's done like this so that the code is portable, it can read in any HTML template.

        Whatever method you use to create the checkboxes should call the same method to validate the checkboxes to check their input. The first

        my $checkbox_fields = get_checkbox_fields(); for my $field (@{$checkbox_fields}) { $in{$field} = $query->param($field) ? 'checked' : 'not checked'; } sub get_checkbox_fields { return [qw(c_test1 c_test2 c_test3)] }

        If you use the get_checkbox_fields() to generate the checkboxes on your web page, then you can use it to verify the data you get from the web page.

        Looping through the fields using a regexp can lead to errors with conflicting field names or someone inserting extra fields into the submission. You can never rely on a naming convention to classify data you receive from a web page. Always know exactly what you expect and you do not have to worry about these sorts of issues.

Re: checkbox help
by Anno (Deacon) on Mar 05, 2007 at 18:23 UTC
    There's too little context for a reasonable atempt to explain the code. For one, the sequence doesn't look right. The first part (you call it a "switch", what do you mean by that?) assumes that %checked already has values. But the values are only set in the code you say runs later. Mind, it may still make sense but it is hard to guess what that could be.

    Why does it matter whether actual text appears in the tab-delimited data? The delimiters make it clear that a field is there, even if there's no data in it. If you must (untested):

    if ($_=~/^c_/) { $check{$_}="CHECKED" print OUT $in{ $_} ? $in{$_} : '-no data-', "\t";

    Your question "Why is $check{$_} being set to "CHECKED" everytime?" doesn't make much sense. $check{$_} is a different hash field every time, it isn't set again and again. As for why this is done, you are in a better position to guess than anyone else.

    Anno

      Here's the whole program,...
      #!/usr/bin/perl use strict; use CGI; use Mail::Sendmail; use File::Basename; use MIME::Base64; my $SERVER="http://".$ENV{'SERVER_NAME'}; my $form=$SERVER.$ENV{'SCRIPT_NAME'} || $SERVER.$ENV{'PATH_INFO'}; my $datapath=dirname $0; my $progname=basename$0; my $smtp = "appsmtp.ottawa.ca"; my ($sec,$min,$hr,$day,$mon,$yr)=(localtime); my $today=sprintf("%04d-%02d-%02d %02d:%02d", $yr+1900, $mon+1, $day, +$hr, $min); my $date; my $start_hide; my $end_hide; my $xls_file = "$datapath/working/file.xls"; my %in=(); my %str=(); my %check=(); my $typetext="text"; my $typetextarea="textarea"; my $closetextarea="<\/textarea>"; my $border=0; my $SENDSTR; my $reset; my $ATTACHSTR; my $MAIL_TO='bryson.connolly@ottawa.ca'; my $submitstr; my %mainbody; my $formid; my $debug =0; my $new_form; #------------------ #read form input #------------------ my $query=new CGI; my $action=$query->param('action'); my $lang=$query->param('lang'); $formid=&newformid; if ($lang ne "_fr") { $lang="_en"; $SENDSTR="Submit"; $reset="Reset"; $ATTACHSTR=''; } else { $SENDSTR="Soumettre"; $reset="reset"; $ATTACHSTR=''; } my $tpl = "registration$lang.html"; $submitstr="<input type=submit name=action value=\"$SENDSTR\"> &nbsp;" +; $reset="<INPUT type=reset value=Reset name=resetbutton> &nbsp;"; print "Content-type: text/html\n\n"; &getdata; if ($action eq $SENDSTR) { $typetext="hidden"; $typetextarea="input type=hidden"; $closetextarea=""; %str=%in; $submitstr=""; $reset=""; $ATTACHSTR=""; $date=$today; mailto($query); } my $tmp= &readhtml("$datapath/$tpl"); #$tmp=&doclean($tmp); print $tmp; exit; #return htmlfile from template sub readhtml { my $selectname; my $retfile; open (DATA, "@_") || print "@_ file not found"; while (<DATA>) { #parse input data #input type must be specified #type specified right after <input #name must only contain "A-Za-z0-9_" s|<input type=text([^>]?name=)"?(\w+)"?(.*?>)|<input type=$typ +etext $1$2 value="$in{$2}" $3 <b><tt>$str{$2}</tt></b>|ig; #s|<textarea([^>]?)name=(\w+)(.*?>)|<$typetextarea $1 name=$2 +value="$in{$2}"$3$in{$2}|ig; s|<textarea(.*)name="?(\w+)"?(.*?>)|<$typetextarea $1 name=$2 +value="$in{$2}" $3 $in{$2}|ig; s|</textarea>|$closetextarea|ig; s|(<input type=radio)(.*)( name=)(\w+)( value="?)([\w\.\s\~]+) +("?)|$1$2$3$4$5$6$7 $check{$4.$6}|ig; s|(<input type=checkbox)(.*)( name=)(\w+)|$1$2$3$4 $check{$4}| +ig; s/(\$[\w{}]+)/$1/eeg; #now to do the selects if ( /<select name="?(\w+)"?/ig ) {$selectname=$1} s/(<option value="?)([\w\@\,\;\.\s\&\/]+)("?)(.*?>)/$1$2$3 $ch +eck{$selectname.$2}$4/ig; s|<option>(.*)</option>| package temp; my $tmp; if ( ($check{$selectname.$1}) or ($typetext eq "text") ) { $tmp="<option $check{$selectname.$1}>$1</option>"; + } $tmp |sgeix; $retfile.=$_; } close (DATA); return $retfile; } sub getdata { # $_ is the form element name and $in{$_} is the cont +ents. if ($action eq $SENDSTR) { open(OUT,">>$xls_file") || print "could not open $xls_file" + ; } foreach ($query->param) { $in{$_} = $query->param($_); if ($_=~/^c_/) { $check{$_}="CHECKED" } #chec +kbox if ($_=~/^r_/) { $check{$_.$in{$_}}="CHECKED" } #radi +o if ($_=~/^s_/) { $check{$_.$in{$_}}="SELECTED" } #Sel +ect if ($action eq $SENDSTR) { $in{$_}=~s/\r\n/ /g; if ($_ !~/action|lang/) { # don't print these fields to X +LS file print OUT "$in{$_}\t"; } } $in{$_}=~s/\r\n/<br>/g || $in{$_}=~s/<br>/\n/g; $in{$_}=~s/"/&quot;/g; $in{$_}=~s/'/&rsquo;/g; $in{$_}=~s/\$/&\#36;/g; } # end foreach if ($action eq $SENDSTR) { print OUT "$today\t"; print OUT "\n"; close(OUT); } } sub mailto { blah blah,,,send mail } sub newformid { use File::CounterFile; $File::CounterFile::DEFAULT_DIR="$datapath/working"; my $c = File::CounterFile->new("counter.dat"); return $c->inc; } sub doclean { #remove the hidden fields and submit/reset buttons ($_)=@_; undef $/; s|<input (type=hidden.*?)>|<skip $1>|imsg; s|<input type="+submit.*?>||imsg; s|<input type="+reset.*?>||imsg; $/="\n"; return $_; }

      So, I want to print "do data" when the checkbox isn't checked. An easier way would be to just name all the checkboxes different. Ex: check1_checked. It won't line up in the spreadsheet, but the data will be there. although, I wouldn't mind figuring out how to print "do data" when it's not checked. I understand that it's not in the param list when it's blank... just trying to figureout the best way to trap that without messing with the program too much.
        thanks for your help guys... i'll just give all the checkboxes descriptive names and those names will be written to the file so i'll know what's checked... just won't line up... but whatever. I'm not re-writing the program just for that.

      Firstly of all, your code will display -no data- when there is data and that data is 0 (zero). Fix:

      print OUT defined($in{$_}) ? $in{$_} : '-no data-', "\t";

      Furthermore, your approach won't work. According to the HTML spec (and verified with a few browsers),

      A switch is "on" when the control element's checked attribute is set. When a form is submitted, only "on" checkbox controls can become successful.

      That means that only checked checkboxes are submitted. defined($in{$_}) will always be true.

        Yeah, sloppy work. Especially since your own first reply reply was already there and might have alerted me. I'll go do something else now.

        Anno

        ok, thanks for you help
Re: checkbox help
by ikegami (Patriarch) on Mar 05, 2007 at 18:05 UTC

    but I want it to write something like "no data" so that everything lines up.

    if (/^c_/) { $check{$_} = "CHECKED" print OUT "$in{$_}\t"; } else { print OUT "no data\t"; # or just "\t" }

    why is $check{$_} being set to "CHECKED" everytime?

    The checkbox will only be present in the list of params if it is checked.

      that didn't work.... because i'm printing out more than just checkbox data. Here's the whole sub routine:
      sub getdata { # $_ is the form element name and $in{$_} is the cont +ents. if ($action eq $SENDSTR) { open(OUT,">>$xls_file") || print "could not open $xls_file" ; } foreach ($query->param) { $in{$_} = $query->param($_); if ($_=~/^c_/) { $check{$_}="CHECKED" } #checkbox if ($_=~/^r_/) { $check{$_.$in{$_}}="CHECKED" } #radio if ($_=~/^s_/) { $check{$_.$in{$_}}="SELECTED" } #Select if ($action eq $SENDSTR) { $in{$_}=~s/\r\n/ /g; if ($_ !~/action|lang/) { # don't print these fields to X +LS file print OUT "$in{$_}\t"; } } $in{$_}=~s/\r\n/<br>/g || $in{$_}=~s/<br>/\n/g; $in{$_}=~s/"/&quot;/g; $in{$_}=~s/'/&rsquo;/g; $in{$_}=~s/\$/&\#36;/g; } # end foreach if ($action eq $SENDSTR) { print OUT "$today\t"; print OUT "\n"; close(OUT); } }

        I have no idea what I was thinking when I wrote the first part of that post. Like I said in the second half, unchecked checkboxes are not in the list of params. As per the HTML spec, they are not sent by the client to the server. You need to loop over the fields you want to output, not over the params

        my @fields = qw( ... c_... c_... c_... ... ); foreach (@fields) { $in{$_} = $query->param($_); if (/^c_/ && defined($in{$_})) { $check{$_} = "CHECKED"; } ... }