--- loncom/homework/grades.pm 2003/09/29 20:58:50 1.130.2.1.2.5 +++ loncom/homework/grades.pm 2003/10/14 22:52:24 1.130.2.1.2.10 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Grading handler # -# $Id: grades.pm,v 1.130.2.1.2.5 2003/09/29 20:58:50 albertel Exp $ +# $Id: grades.pm,v 1.130.2.1.2.10 2003/10/14 22:52:24 albertel Exp $ # # Copyright Michigan State University Board of Trustees # @@ -3146,17 +3146,18 @@ sub getSequenceDropDown { } sub scantron_uploads { - #FIXME need to support scantron files put in another location, - # maybe the course directory? a scantron dir in the course directory? if (!-e $Apache::lonnet::perlvar{'lonScansDir'}) { return ''}; my $result= '"; return $result; } @@ -3276,39 +3277,42 @@ sub username_to_idmap { } sub scantron_fixup_scanline { - my ($scantron_config,$scan_data,$line,$field,$newvalue,$arg) = @_; + my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_; if ($field eq 'ID') { - if ($newvalue > $$scantron_config{'IDlength'}) { + if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) { return ($line,1,'New value to large'); } - if ($newvalue < $$scantron_config{'IDlength'}) { - $newvalue=sprintf('%-'.$$scantron_config{'IDlength'}.'s', - $newvalue); + if (length($args->{'newid'}) < $$scantron_config{'IDlength'}) { + $args->{'newid'}=sprintf('%-'.$$scantron_config{'IDlength'}.'s', + $args->{'newid'}); } substr($line,$$scantron_config{'IDstart'}-1, - $$scantron_config{'IDlength'})=$newvalue; + $$scantron_config{'IDlength'})=$args->{'newid'}; + if ($args->{'newid'}=~/^\s*$/) { + &scan_data($scan_data,"$whichline.user", + $args->{'username'}.':'.$args->{'domain'}); + } } elsif ($field eq 'answer') { my $length=$scantron_config->{'Qlength'}; my $off=$scantron_config->{'Qoff'}; my $on=$scantron_config->{'Qon'}; my $answer=${off}x$length; - if ($arg ne 'none') { - substr($answer,$arg,1)=$on; - &scan_data($scan_data,"no_bubble.$newvalue",undef,'1'); + if ($args->{'response'} eq 'none') { + &scan_data($scan_data, + "$whichline.no_bubble.".$args->{'question'},'1'); } else { - &scan_data($scan_data,"no_bubble.$newvalue",'1'); + substr($answer,$args->{'response'},1)=$on; + &scan_data($scan_data, + "$whichline.no_bubble.".$args->{'question'},undef,'1'); } - my $where=$length*($newvalue-1)+$scantron_config->{'Qstart'}; - Apache->request->print("where $where arg $arg "); - Apache->request->print('b:
'.$line.'
'); + my $where=$length*($args->{'question'}-1)+$scantron_config->{'Qstart'}; substr($line,$where-1,$length)=$answer; - Apache->request->print('a:
'.$line.'
'); } return $line; } sub scan_data { - my ($scan_data,$key,$value,$delete); + my ($scan_data,$key,$value,$delete)=@_; my $filename=$ENV{'form.scantron_selectfile'}; if (defined($value)) { $scan_data->{$filename.'_'.$key} = $value; @@ -3318,7 +3322,7 @@ sub scan_data { } sub scantron_parse_scanline { - my ($line,$scantron_config,$scan_data)=@_; + my ($line,$whichline,$scantron_config,$scan_data)=@_; my %record; my $questions=substr($line,$$scantron_config{'Qstart'}-1); my $data=substr($line,0,$$scantron_config{'Qstart'}-1); @@ -3351,7 +3355,7 @@ sub scantron_parse_scanline { my @array=split($$scantron_config{'Qon'},$currentquest,-1); if (length($array[0]) eq $$scantron_config{'Qlength'}) { $record{"scantron.$questnum.answer"}=''; - if (!&scan_data($scan_data,"no_bubble.$questnum")) { + if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) { push(@{$record{"scantron.missingerror"}},$questnum); } } else { @@ -3361,7 +3365,7 @@ sub scantron_parse_scanline { push(@{$record{'scantron.doubleerror'}},$questnum); my @ans=@array; my $i=length($ans[0]);shift(@ans); - while (@ans) { + while ($#ans) { $i+=length($ans[0])+1; $record{"scantron.$questnum.answer"}.=$alphabet[$i]; shift(@ans); @@ -3374,7 +3378,6 @@ sub scantron_parse_scanline { sub scantron_add_delay { my ($delayqueue,$scanline,$errormessage,$errorcode)=@_; - Apache->request->print('add_delay_error '.$_[2] ); push(@$delayqueue, {'line' => $scanline, 'emsg' => $errormessage, 'ecode' => $errorcode } @@ -3382,12 +3385,13 @@ sub scantron_add_delay { } sub scantron_find_student { - my ($scantron_record,$idmap)=@_; + my ($scantron_record,$scan_data,$idmap,$line)=@_; my $scanID=$$scantron_record{'scantron.ID'}; + if ($scanID =~ /^\s*$/) { + return &scan_data($scan_data,"$line.user"); + } foreach my $id (keys(%$idmap)) { - #Apache->request->print('
checking studnet -'.$id.'- againt -'.$scanID.'- 
'); if (lc($id) eq lc($scanID)) { - #Apache->request->print('success'); return $$idmap{$id}; } } @@ -3402,14 +3406,6 @@ sub scantron_filter { return 0; } -#FIXME I think I am doing this in the wrong order, I think it would be -#better to make a several passes analyzing all of the lines in the -#file for common errors wrong/invalid PID/username duplicated -#PID/username, missing bubbles, double bubbles, missing/invalid CODE -#and then get the instructor to fix all of these errors, then grade -#the corrected one, I'll still need to catch error conditions, but -#maybe most will taken care even before we start - sub scantron_process_corrections { my ($r) = @_; my %scantron_config=&get_scantron_config($ENV{'form.scantron_format'}); @@ -3425,14 +3421,17 @@ sub scantron_process_corrections { $ENV{'form.scantron_domain'}; my $newid=$classlist->{$newstudent}->[&Apache::loncoursedata::CL_ID]; ($line,$err,$errmsg)= - &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,'ID', - $newid); + &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which, + 'ID',{'newid'=>$newid, + 'username'=>$ENV{'form.scantron_username'}, + 'domain'=>$ENV{'form.scantron_domain'}}); } elsif ($ENV{'form.scantron_corrections'} =~ /^(missing|double)bubble$/) { foreach my $question (split(',',$ENV{'form.scantron_questions'})) { ($line,$err,$errmsg)= &scantron_fixup_scanline(\%scantron_config,$scan_data,$line, - 'answer',$question, - $ENV{"form.scantron_correct_Q_$question"}); + $which,'answer', + { 'question'=>$question, + 'response'=>$ENV{"form.scantron_correct_Q_$question"}}); if ($err) { last; } } } @@ -3458,7 +3457,6 @@ sub scantron_validate_file { $r->print(&Apache::loncommon::studentbrowser_javascript()); my $result= < - @@ -3482,18 +3480,34 @@ SCANTRONFORM } my $stop=0; while (!$stop && $currentphase < scalar(@validate_phases)) { + $r->print("

Validating ".$validate_phases[$currentphase]."

"); + $r->rflush(); my $which="scantron_validate_".$validate_phases[$currentphase]; { no strict 'refs'; ($stop,$currentphase)=&$which($r,$currentphase); } } - $r->print(""); + if (!$stop) { + $r->print("Validation process complete.
"); + $r->print(''); + $r->print(''); + } else { + $r->print(''); + $r->print(""); + } + if ($stop) { + $r->print(''); + $r->print(' using corrected info
'); + $r->print(""); + $r->print(" this scanline saving it for later."); + } + $r->print("
".&show_grading_menu_form($symb,$url). + ""); return ''; } sub scantron_getfile { - #my $scanlines=Apache::File->new($Apache::lonnet::perlvar{'lonScansDir'}."/$ENV{'form.scantron_selectfile'}"); #FIXME really would prefer a scantron directory but tokenwrapper # doesn't allow access to subdirs of userfiles my $cname=$ENV{'course.'.$ENV{'request.course.id'}.'.num'}; @@ -3502,10 +3516,11 @@ sub scantron_getfile { $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'. 'scantron_orig_'.$ENV{'form.scantron_selectfile'}); if ($lines eq '-1') { - #FIXME need to actually replicate file to course space + #FIXME need to actually replicate file to course space + #FIXME when replicating strip CRLF to LF or CR to LF } my %scanlines; - $scanlines{'orig'}=[split("\n",$lines)]; + $scanlines{'orig'}=[(split("\n",$lines,-1))]; my $temp=$scanlines{'orig'}; $scanlines{'count'}=$#$temp; @@ -3514,14 +3529,14 @@ sub scantron_getfile { if ($lines eq '-1') { $scanlines{'corrected'}=[]; } else { - $scanlines{'corrected'}=[split("\n",$lines)]; + $scanlines{'corrected'}=[(split("\n",$lines,-1))]; } $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'. 'scantron_skipped_'.$ENV{'form.scantron_selectfile'}); if ($lines eq '-1') { $scanlines{'skipped'}=[]; } else { - $scanlines{'skipped'}=[split("\n",$lines)]; + $scanlines{'skipped'}=[(split("\n",$lines,-1))]; } my @tmp=&Apache::lonnet::dump('scantrondata',$cdom,$cname); if ($tmp[0] =~ /^(error:|no_such_host)/) { @tmp=(); } @@ -3567,7 +3582,10 @@ sub scantron_get_line { sub scantron_put_line { my ($scanlines,$i,$newline,$skip)=@_; - if ($skip) { $scanlines->{'skipped'}[$i]=$newline;return; } + if ($skip) { + $scanlines->{'skipped'}[$i]=$newline; + return; + } $scanlines->{'corrected'}[$i]=$newline; } @@ -3585,33 +3603,48 @@ sub scantron_validate_ID { my %found=('ids'=>{},'usernames'=>{}); for (my $i=0;$i<=$scanlines->{'count'};$i++) { my $line=&scantron_get_line($scanlines,$i); - if (!$line) { next; } - my $scan_record=&scantron_parse_scanline($line,\%scantron_config,$scan_data); + if ($line=~/^[\s\cz]*$/) { next; } + my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config, + $scan_data); my $id=$$scan_record{'scantron.ID'}; - $r->print("

Checking ID ".$$scan_record{'scantron.ID'}. - " on paper ID ".$$scan_record{'scantron.PaperID'}."

\n"); my $found; foreach my $checkid (keys(%idmap)) { - if (lc($checkid) eq lc($id)) { - if ($checkid ne $id) { - $r->print("

Using $checkid for encoded $id

\n"); - } - $found=$checkid;last; - } + if (lc($checkid) eq lc($id)) { $found=$checkid;last; } } if ($found) { + my $username=$idmap{$found}; if ($found{'ids'}{$found}) { - #FIXME store away line we prviously saw the ID on &scantron_get_correction($r,$i,$scan_record,\%scantron_config, $line,'duplicateID',$found); return(1); - } else { - $found{'ids'}{$found}++; + } elsif ($found{'usernames'}{$username}) { + &scantron_get_correction($r,$i,$scan_record,\%scantron_config, + $line,'duplicateID',$username); + return(1); } + #FIXME store away line we prviously saw the ID on to use above + $found{'ids'}{$found}++; + $found{'usernames'}{$username}++; } else { - &scantron_get_correction($r,$i,$scan_record,\%scantron_config, - $line,'incorrectID'); - return(1); + if ($id =~ /^\s*$/) { + my $username=&scan_data($scan_data,"$i.user"); + if (defined($username) && $found{'usernames'}{$username}) { + &scantron_get_correction($r,$i,$scan_record, + \%scantron_config, + $line,'duplicateID',$username); + return(1); + } elsif (!defined($username)) { + &scantron_get_correction($r,$i,$scan_record, + \%scantron_config, + $line,'incorrectID'); + return(1); + } + $found{'usernames'}{$username}++; + } else { + &scantron_get_correction($r,$i,$scan_record,\%scantron_config, + $line,'incorrectID'); + return(1); + } } } @@ -3625,12 +3658,12 @@ sub scantron_get_correction { #to show both the current line and the previous one and allow skipping #the previous one or the current one - $r->print("

This scantron record has an error ($error). "); + $r->print("

An error was detected ($error) "); if ( defined($$scan_record{'scantron.PaperID'}) ) { - $r->print("The current PaperID is ". + $r->print(" for PaperID ". $$scan_record{'scantron.PaperID'}." \n"); } else { - $r->print("The current scanline is

".
+	$r->print(" in scanline $i 
".
 		  $line."
\n"); } $r->print(''."\n"); @@ -3641,58 +3674,68 @@ sub scantron_get_correction { } elsif ($error eq 'duplicateID') { $r->print("The encoded ID has also been used by a previous paper $arg

\n"); } - $r->print("

Original ID is ".$$scan_record{'scantron.ID'}. - "
\n"); - $r->print("Name on paper is ".$$scan_record{'scantron.LastName'}.",". + $r->print("

The ID on the form is ". + $$scan_record{'scantron.ID'}."
\n"); + $r->print("The name on the paper is ". + $$scan_record{'scantron.LastName'}.",". $$scan_record{'scantron.FirstName'}."

"); - $r->print("

Please correct
\n"); - $r->print("\n

  • Pick a specific user -- username:"); - $r->print("\ndomain:". - &Apache::loncommon::select_dom_form(undef,'scantron_domain')); + $r->print("

    How should I handle this?
    \n"); + $r->print("\n

    • "); #FIXME it would be nice if this sent back the user ID and #could do partial userID matches $r->print(&Apache::loncommon::selectstudent_link('scantronupload', - 'scantron_username','scantron_domain')); + 'scantron_username','scantron_domain')); + $r->print(": "); + $r->print("\n@". + &Apache::loncommon::select_dom_form(undef,'scantron_domain')); + $r->print('
    • '); } elsif ($error eq 'doublebubble') { - $r->print("There have been multiple bubbles scanned for a single question\n"); +#FIXME Need to print out who this is along with the paper info + $r->print("

      There have been multiple bubbles scanned for a some question(s)

      \n"); $r->print(''); + $r->print("

      Please indicate which bubble should be used for grading

      "); foreach my $question (@{$arg}) { my $selected=$$scan_record{"scantron.$question.answer"}; - $r->print("

      For question $question, selected bubbles were ". - join(" ",split('',$selected)). - "
      Please pick which one should be used for grading
      "); - &scantron_bubble_selector($r,$scan_config,$question); + &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"); + $r->print("

      Please indicate which bubble should be used for grading

      "); $r->print("Some questions have no scanned bubbles\n"); $r->print(''); foreach my $question (@{$arg}) { my $selected=$$scan_record{"scantron.$question.answer"}; - $r->print("

      Question $question, Please select a bubble to use "); &scantron_bubble_selector($r,$scan_config,$question); } } else { $r->print("\n

        "); } - $r->print("
      • Skip this scanline saving it for later "); - $r->print("\n
      "); - &scantron_end_validate_form($r); + $r->print("\n
    "); + } sub scantron_bubble_selector { - my ($r,$scan_config,$quest)=@_; + my ($r,$scan_config,$quest,@selected)=@_; my $max=$$scan_config{'Qlength'}; my @alphabet=('A'..'Z'); + $r->print(""); + for (my $i=0;$i<$max+1;$i++) { + $r->print(''); + } + $r->print(''); for (my $i=0;$i<$max;$i++) { - $r->print(''.$alphabet[$i]); + $r->print('"); } - $r->print(' Nothing'); - $r->print('
    '); + $r->print(''); + $r->print('
    $quest'); + if ($selected[0] eq $alphabet[$i]) { $r->print('X'); shift(@selected) } + else { $r->print(' '); } + $r->print('
    '.$alphabet[$i]." No bubble
    '); } sub scantron_validate_CODE { @@ -3712,8 +3755,9 @@ sub scantron_validate_doublebubble { my ($scanlines,$scan_data)=&scantron_getfile(); for (my $i=0;$i<=$scanlines->{'count'};$i++) { my $line=&scantron_get_line($scanlines,$i); - if (!$line) { next; } - my $scan_record=&scantron_parse_scanline($line,\%scantron_config,$scan_data); + if ($line=~/^[\s\cz]*$/) { next; } + my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config, + $scan_data); if (!defined($$scan_record{'scantron.doubleerror'})) { next; } &scantron_get_correction($r,$i,$scan_record,\%scantron_config,$line, 'doublebubble', @@ -3736,12 +3780,13 @@ sub scantron_validate_missingbubbles { if (!$max_bubble) { $max_bubble=2**31; } for (my $i=0;$i<=$scanlines->{'count'};$i++) { my $line=&scantron_get_line($scanlines,$i); - if (!$line) { next; } - my $scan_record=&scantron_parse_scanline($line,\%scantron_config,$scan_data); + if ($line=~/^[\s\cz]*$/) { next; } + my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config, + $scan_data); if (!defined($$scan_record{'scantron.missingerror'})) { next; } my @to_correct; foreach my $missing (@{$$scan_record{'scantron.missingerror'}}) { - if ($missing gt $max_bubble) { next; } + if ($missing > $max_bubble) { next; } push(@to_correct,$missing); } if (@to_correct) { @@ -3754,11 +3799,6 @@ sub scantron_validate_missingbubbles { return (0,$currentphase+1); } -sub scantron_end_validate_form { - my ($r) = @_; - $r->print(''); -} - sub scantron_process_students { my ($r) = @_; my (undef,undef,$sequence)=split(/___/,$ENV{'form.selectpage'}); @@ -3789,15 +3829,18 @@ SCANTRONFORM &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state, 'Processing first student'); my $start=&Time::HiRes::time(); - my $i=0; - while ($i<=$scanlines->{'count'}) { + my $i=-1; + my ($uname,$udom); + while ($i<$scanlines->{'count'}) { + ($uname,$udom)=('',''); $i++; my $line=&scantron_get_line($scanlines,$i); - if (!$line) { next; } - $r->print('
    line is'.$line.'
    '); - my $scan_record=&scantron_parse_scanline($line,\%scantron_config,$scan_data); - my ($uname,$udom); - unless ($uname=&scantron_find_student($scan_record,\%idmap)) { +# $r->print('
    line is'.$line.'
    '); + if ($line=~/^[\s\cz]*$/) { next; } + my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config, + $scan_data); + unless ($uname=&scantron_find_student($scan_record,$scan_data, + \%idmap,$i)) { &scantron_add_delay(\@delayqueue,$line, 'Unable to find a student that matches',1); next; @@ -3807,7 +3850,7 @@ SCANTRONFORM 'Student '.$uname.' has multiple sheets',2); next; } - $r->print('
    doing studnet'.$uname.'
    '); +# $r->print('
    doing studnet'.$uname.'
    '); ($uname,$udom)=split(/:/,$uname); &Apache::lonnet::delenv('form.counter'); &Apache::lonnet::appenv(%$scan_record); @@ -3847,8 +3890,7 @@ SCANTRONFORM } continue { &Apache::lonnet::delenv('form.counter'); &Apache::lonnet::delenv('scantron\.'); - &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state, - 'last student'); + &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,$uname); #last; #FIXME #get iterator for $sequence @@ -3859,7 +3901,7 @@ SCANTRONFORM } &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); my $lasttime = &Time::HiRes::time()-$start; - $r->print("

    took $lasttime

    "); + #$r->print("

    took $lasttime

    "); #$Apache::lonxml::debug=0; foreach my $delay (@delayqueue) { @@ -3876,7 +3918,55 @@ SCANTRONFORM # to ignore delayed students, possibly saving the delay queue for later $navmap->untieHashes(); + $r->print("

    Done

    "); + $r->print(&show_grading_menu_form($symb,$url)); + return ''; +} + +sub scantron_upload_scantron_data { + my ($r)=@_; + $r->print(&Apache::loncommon::coursebrowser_javascript($ENV{'request.role.domain'})); + my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid', + 'domainid'); + my $domsel=&Apache::loncommon::select_dom_form($ENV{'request.role.domain'}, + 'domainid'); + $r->print(< + function checkUpload(formname) { + if (formname.upfile.value == "") { + alert("Please use the browse button to select a file from your local directory."); + return false; + } + formname.submit(); + } + + +
    +Course: +Domain: $domsel $select_link +
    + +File to upload: +
    + +
    +UPLOAD + return ''; } + +sub scantron_upload_scantron_data_save { + my($r)=@_; + $r->print("Doing upload to ".$ENV{'form.courseid'}); + my $home=&Apache::lonnet::homeserver($ENV{'form.courseid'}, + $ENV{'form.domainid'}); + my $fname='scantron_orig_'.$ENV{'form.upfile.filename'}; + $r->print(&Apache::lonnet::finishuserfileupload($ENV{'form.courseid'}, + $ENV{'form.domainid'}, + $home,'upfile',$fname)); + return ''; +} + + #-------- end of section for handling grading scantron forms ------- # #------------------------------------------------------------------- @@ -4063,7 +4153,7 @@ sub handler { $url = $ENV{'form.url'}; } &send_header($request); - if ($url eq '' && $symb eq '') { + if ($url eq '' && $symb eq '' && $command eq '') { if ($ENV{'user.adv'}) { if (($ENV{'form.codeone'}) && ($ENV{'form.codetwo'}) && ($ENV{'form.codethree'})) { @@ -4104,7 +4194,6 @@ sub handler { delete($perm{'mgr'}); } } - if ($command eq 'submission' && $perm{'vgr'}) { ($ENV{'form.student'} eq '' ? &listStudents($request) : &submission($request,0,0)); } elsif ($command eq 'pickStudentPage' && $perm{'vgr'}) { @@ -4148,8 +4237,15 @@ sub handler { $request->print(&scantron_validate_file($request)); } elsif ($command eq 'scantron_process' && $perm{'mgr'}) { $request->print(&scantron_process_students($request)); + } elsif ($command eq 'scantronupload' && + &Apache::lonnet::allowed('usc',$ENV{'request.role.domain'})) { + $request->print(&scantron_upload_scantron_data($request)); + + } elsif ($command eq 'scantronupload_save' && + &Apache::lonnet::allowed('usc',$ENV{'request.role.domain'})) { + $request->print(&scantron_upload_scantron_data_save($request)); } elsif ($command) { - $request->print("Access Denied"); + $request->print("$command ".join(':',%perm)."Access Denied"); } } &send_footer($request); @@ -4165,6 +4261,7 @@ sub send_header { #remotewindow.close(); #"); $request->print(&Apache::loncommon::bodytag('Grading')); + $request->rflush(); } sub send_footer {