--- loncom/homework/grades.pm 2007/06/22 23:45:19 1.418 +++ loncom/homework/grades.pm 2007/07/19 09:52:59 1.422 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Grading handler # -# $Id: grades.pm,v 1.418 2007/06/22 23:45:19 albertel Exp $ +# $Id: grades.pm,v 1.422 2007/07/19 09:52:59 foxr Exp $ # # Copyright Michigan State University Board of Trustees # @@ -4354,6 +4354,8 @@ sub updateGradeByPage { # #------ start of section for handling grading by page/sequence --------- +# Create the hidden field entries used to hold context/default values. + sub defaultFormData { my ($symb)=@_; return ' @@ -4362,6 +4364,8 @@ sub defaultFormData { ''."\n"; } +# Make a drop down of the sequences + sub getSequenceDropDown { my ($request,$symb)=@_; my $result=''; @@ -4405,6 +4414,9 @@ sub scantron_uploads { return $result; } +# Returns the html for a drop down list of the scantron formats in the +# scantronformat.tab file. + sub scantron_scantab { my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab'); my $result=''; return $result; } +# +# Display the first scantron file selection form. +# Paramters: +# r - The apache request object +# file2grade - The name of the scantron file to be graded(?). sub scantron_selectphase { my ($r,$file2grade) = @_; @@ -4459,6 +4481,9 @@ sub scantron_selectphase { my $result; #FIXME allow instructor to be able to download the scantron file # and to upload it, + + # Chunk of form to prompt for a file to grade and how: + $result.= < @@ -4511,6 +4536,8 @@ SCANTRONFORM if (&Apache::lonnet::allowed('usc',$env{'request.role.domain'}) || &Apache::lonnet::allowed('usc',$env{'request.course.id'})) { + # Chunk of form to prompt for a scantron file upload. + $r->print(< @@ -4556,6 +4583,10 @@ UPLOAD SCANTRONFORM } + + # Chunk of the form that prompts to view a scoring office file, + # corrected file, skipped records in a file. + $r->print(<
@@ -4590,6 +4621,14 @@ SCANTRONFORM return } +# Parse and return the scantron configuration line selected as a +# hash of configuration file fields. +# +# Parameters: +# which - the name of the configuration to parse from the file. +# If the named configuration is not in the file, an empty +# hash is returned. + sub get_scantron_config { my ($which) = @_; my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab'); @@ -4622,6 +4661,15 @@ sub get_scantron_config { return %config; } +# creates a hash keyed by student id that conains +# the corresponding student username:domain. +# Parameters: +# reference to the class list hash. This is a hash +# keyed by student name:domain whose elements are references +# to arrays containng various chunks of information +# about the student. (See loncoursedata for more info). +# +# sub username_to_idmap { my ($classlist)= @_; my %idmap; @@ -4631,9 +4679,22 @@ sub username_to_idmap { } return %idmap; } +# +# Make a correction in a scantron line? +# Parameters: +# scantron_config - Format of the scantron file +# scan_data - Hash of line by line info about the scan(?). +# line - Scantron line to edit? +# whichline +# field +# args - Keyword/value hash of additional parameters. +# sub scantron_fixup_scanline { my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_; + # + # ID field, args->{'newid'} is the new value of the ID field. + # if ($field eq 'ID') { if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) { return ($line,1,'New value too large'); @@ -4648,6 +4709,11 @@ sub scantron_fixup_scanline { &scan_data($scan_data,"$whichline.user", $args->{'username'}.':'.$args->{'domain'}); } + # CODE Field, + # args->{CODE_ignore_dup} is true if duplicates should be ignored. + # args->{CODE} is new code or 'use_unfound' if an unfound code should + # be used as is? + # } elsif ($field eq 'CODE') { if ($args->{'CODE_ignore_dup'}) { &scan_data($scan_data,"$whichline.CODE_ignore_dup",'1'); @@ -4663,6 +4729,11 @@ sub scantron_fixup_scanline { substr($line,$$scantron_config{'CODEstart'}-1, $$scantron_config{'CODElength'})=$args->{'CODE'}; } + # + # Edit the answer field. + # args->{'response'} - new answer or 'none' if blank. + # args->{'question'} - the question (number?)?. + # } elsif ($field eq 'answer') { my $length=$scantron_config->{'Qlength'}; my $off=$scantron_config->{'Qoff'}; @@ -4689,7 +4760,16 @@ sub scantron_fixup_scanline { } return $line; } - +# Edit or look up an item in the scan_data hash. +# Parameters: +# scan_data - The hash. +# key - shorthand of the key to edit (actual key is +# scatronfilename_key. +# data - New value of the hash entry. +# delete - If defined, the entry is removed from the table. +# Returns: +# The new value of the hash table field (undefined if deleted). +# sub scan_data { my ($scan_data,$key,$value,$delete)=@_; my $filename=$env{'form.scantron_selectfile'}; @@ -4699,12 +4779,23 @@ sub scan_data { if ($delete) { delete($scan_data->{$filename.'_'.$key}); } return $scan_data->{$filename.'_'.$key}; } - +# +# Decode a line on the uploaded scantron file: +# Arguments: +# line - The text of the scantron file line to process +# whichline - Line number(?) +# scantron_config - Hash describing the format of the scantron lines. +# scan_data - Hash being built up of the entire scantron file. +# justHeader - True if should not process question answers but only +# the stuff to the left of the answers. +# Returns: +# Hash of data from the line? +# sub scantron_parse_scanline { my ($line,$whichline,$scantron_config,$scan_data,$justHeader)=@_; my %record; - my $questions=substr($line,$$scantron_config{'Qstart'}-1); - my $data=substr($line,0,$$scantron_config{'Qstart'}-1); + my $questions=substr($line,$$scantron_config{'Qstart'}-1); # Answers + my $data=substr($line,0,$$scantron_config{'Qstart'}-1); # earlier stuff if (!($$scantron_config{'CODElocation'} eq 0 || $$scantron_config{'CODElocation'} eq 'none')) { if ($$scantron_config{'CODElocation'} < 0 || @@ -5456,7 +5547,8 @@ ENDSCRIPT $r->print("

Please indicate which bubble should be used for grading

"); foreach my $question (@{$arg}) { my $selected=$$scan_record{"scantron.$question.answer"}; - &scantron_bubble_selector($r,$scan_config,$question,split('',$selected)); + &scantron_bubble_selector($r,$scan_config,$question, + split('',$selected)); } } elsif ($error eq 'missingbubble') { $r->print("

There have been no bubbles scanned for some question(s)

\n"); @@ -5475,31 +5567,74 @@ ENDSCRIPT $r->print("\n"); } - +# +# Ask the grader to select the actual bubble +# +# Arguments: +# r - Apache request. +# scan_config - Hash of the scantron format selected. +# quest - Question being evaluated +# selected - array of selected bubbles +# lines - if present, number of bubble lines in questions. sub scantron_bubble_selector { - my ($r,$scan_config,$quest,@selected)=@_; + my ($r,$scan_config,$quest,@selected, $lines)=@_; my $max=$$scan_config{'Qlength'}; my $scmode=$$scan_config{'Qon'}; if ($scmode eq 'number' || $scmode eq 'letter') { $max=10; } - my @alphabet=('A'..'Z'); - $r->print(""); - for (my $i=0;$i<$max+1;$i++) { - $r->print("\n".''); - } - $r->print(''); - for (my $i=0;$i<$max;$i++) { - $r->print("\n". - '"); + + if (!defined($lines)) { + $lines = 1; } - $r->print('"); + + for (my $l = 0; $l < $lines; $l++) { + if ($l != 0) { + $r->print(''); + } + + # FIXME: This loop probably has to be considerably more clever for + # multiline bubbles: User can multibubble by having bubbles in + # several lines. User can skip lines legitimately etc. etc. + + for (my $i=0;$i<$max;$i++) { + $r->print("\n".''); + + } + + if ($l == 0) { + my $lspan = $total_lines * 2; # 2 table rows per bubble line. + + $r->print(''); - $r->print('
$quest'); - if ($selected[0] eq $alphabet[$i]) { $r->print('X'); shift(@selected) } - else { $r->print(' '); } - $r->print('
$quest
'); + if ($selected[0] eq $alphabet[$i]) { + $r->print('X'); + shift(@selected) ; + } else { + $r->print(' '); + } + $r->print('
'); + + } + + $r->print(''); + + # FIXME: This may have to be a bit more clever for + # multiline questions (different values e.g..). + + for (my $i=0;$i<$max;$i++) { + $r->print("\n". + '"); + } + $r->print(''); + + + } + $r->print(''); } sub num_matches { @@ -6133,6 +6268,7 @@ sub gather_clicker_ids { (&Apache::lonnet::userenvironment($domain,$username,'clickers'))[1]; foreach my $id (split(/\,/,$clickers)) { $id=~s/^[\#0]+//; + $id=~s/[\-\:]//g; if (exists($clicker_ids{$id})) { $clicker_ids{$id}.=','.$username.':'.$domain; } else { @@ -6155,6 +6291,7 @@ sub gather_adv_clicker_ids { (&Apache::lonnet::userenvironment($pudom,$puname,'clickers'))[1]; foreach my $id (split(/\,/,$clickers)) { $id=~s/^[\#0]+//; + $id=~s/[\-\:]//g; if (exists($clicker_ids{$id})) { $clicker_ids{$id}.=','.$puname.':'.$pudom; } else { @@ -6211,7 +6348,8 @@ sub process_clicker { my $pcorrect=&mt("Percentage points for correct solution"); my $pincorrect=&mt("Percentage points for incorrect solution"); my $selectform=&Apache::loncommon::select_form($env{'form.upfiletype'},'upfiletype', - ('iclicker' => 'i>clicker')); + ('iclicker' => 'i>clicker', + 'interwrite' => 'interwrite PRS')); $symb = &Apache::lonenc::check_encrypt($symb); $result.=< @@ -6300,6 +6438,7 @@ sub process_clicker_file { $correct_id=~tr/a-z/A-Z/; $correct_id=~s/\s//gs; $correct_id=~s/^[\#0]+//; + $correct_id=~s/[\-\:]//g; if ($correct_id) { $correct_ids{$correct_id}='specified'; } @@ -6359,6 +6498,9 @@ ENDHEADER if ($env{'form.upfiletype'} eq 'iclicker') { ($errormsg,$number)=&iclicker_eval(\@questiontitles,\%responses); } + if ($env{'form.upfiletype'} eq 'interwrite') { + ($errormsg,$number)=&interwrite_eval(\@questiontitles,\%responses); + } $result.='
'.&mt('Found [_1] question(s)',$number).'
'. ''. &mt('Awarding [_1] percent for correct and [_2] percent for incorrect responses', @@ -6438,6 +6580,37 @@ sub iclicker_eval { return ($errormsg,$number); } +sub interwrite_eval { + my ($questiontitles,$responses)=@_; + my $number=0; + my $errormsg=''; + my $skipline=1; + my $questionnumber=0; + my %idresponses=(); + foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) { + my %components=&Apache::loncommon::record_sep($line); + my @entries=map {$components{$_}} (sort(keys(%components))); + if ($entries[1] eq 'Time') { $skipline=0; next; } + if ($entries[1] eq 'Response') { $skipline=1; } + next if $skipline; + if ($entries[0]!=$questionnumber) { + $questionnumber=$entries[0]; + $$questiontitles[$number]=&mt('Question [_1]',$questionnumber); + $number++; + } + my $id=$entries[4]; + $id=~s/^[\#0]+//; + $id=~s/^v\d*\://i; + $id=~s/[\-\:]//g; + $idresponses{$id}[$number]=$entries[6]; + } + foreach my $id (keys %idresponses) { + $$responses{$id}=join(',',@{$idresponses{$id}}); + $$responses{$id}=~s/^\s*\,//; + } + return ($errormsg,$number); +} + sub assign_clicker_grades { my ($r)=@_; my ($symb)=&get_symb($r); @@ -6490,8 +6663,17 @@ ENDHEADER my $pincorrect=$env{'form.pincorrect'}; my $storecount=0; foreach my $key (keys(%env)) { + my $user=''; if ($key=~/^form\.student\:(.*)$/) { - my $user=$1; + $user=$1; + } + if ($key=~/^form\.unknown\:(.*)$/) { + my $id=$1; + if (($env{'form.uname'.$id}) && ($env{'form.udom'.$id})) { + $user=$env{'form.uname'.$id}.':'.$env{'form.udom'.$id}; + } + } + if ($user) { my @answer=split(/\,/,$env{$key}); my $sum=0; for (my $i=0;$i<$number;$i++) {