');
+ $r->print(&mt('Validating '.$validate_phases[$currentphase]).' ');
$r->rflush();
my $which="scantron_validate_".$validate_phases[$currentphase];
{
@@ -5964,7 +6031,7 @@ sub scantron_validate_file {
if (!$stop) {
my $warning=&scantron_warning_screen('Start Grading');
$r->print('
-'.&mt('Validation process complete.').'
+'.&mt('Validation process complete.').'
'.$warning.'
@@ -5981,7 +6048,11 @@ sub scantron_validate_file {
$r->print("
".&mt("Or click the 'Grading Menu' button to start over.")."
");
} else {
- $r->print('');
+ if ($validate_phases[$currentphase] eq 'doublebubble' || $validate_phases[$currentphase] eq 'missingbubbles') {
+ $r->print('');
+ } else {
+ $r->print('');
+ }
$r->print(' '.&mt('using corrected info').' ');
$r->print("");
$r->print(" ".&mt("this scanline saving it for later."));
@@ -6463,7 +6534,6 @@ sub scantron_validate_ID {
sub scantron_get_correction {
my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_;
-
#FIXME in the case of a duplicated ID the previous line, probably need
#to show both the current line and the previous one and allow skipping
#the previous one or the current one
@@ -6485,6 +6555,10 @@ sub scantron_get_correction {
$r->print(''."\n");
$r->print(''."\n");
+ # Array populated for doublebubble or
+ my @lines_to_correct; # missingbubble errors to build javascript
+ # to validate radio button checking
+
if ($error =~ /ID$/) {
if ($error eq 'incorrectID') {
$r->print("
".&mt("The encoded ID is not in the classlist").
@@ -6580,7 +6654,7 @@ ENDSCRIPT
""));
$r->print("\n
".&mt("There have been multiple bubbles scanned for a some question(s)")."
\n");
+ $r->print("
".&mt("There have been multiple bubbles scanned for some question(s)")."
\n");
# The form field scantron_questions is acutally a list of line numbers.
# represented by this form so:
@@ -6592,15 +6666,18 @@ ENDSCRIPT
$r->print($message);
$r->print("
".&mt("Please indicate which bubble should be used for grading")."
".&mt("There have been no bubbles scanned for some question(s)")."
\n");
$r->print($message);
$r->print("
".&mt("Please indicate which bubble should be used for grading.")."
");
- $r->print(&mt("Some questions have no scanned bubbles")."\n");
+ $r->print(&mt("Some questions have no scanned bubbles.")."\n");
- # The form field scantron_questinos is actually a list of line numbers not
+ # The form field scantron_questions is actually a list of line numbers not
# a list of question numbers. Therefore:
#
@@ -6609,14 +6686,50 @@ ENDSCRIPT
$r->print('');
foreach my $question (@{$arg}) {
- &prompt_for_corrections($r, $question, $scan_config, $scan_record);
+ my @linenums = &prompt_for_corrections($r,$question,$scan_config,
+ $scan_record, $error);
+ push (@lines_to_correct,@linenums);
}
+ $r->print(&verify_bubbles_checked(@lines_to_correct));
} else {
$r->print("\n
");
}
$r->print("\n
");
}
+sub verify_bubbles_checked {
+ my (@ansnums) = @_;
+ my $ansnumstr = join('","',@ansnums);
+ my $warning = &mt("A bubble or 'No bubble' selection has not been made for one or more lines.");
+ my $output = (<
+function verify_bubble_radio(form) {
+ var ansnumArray = new Array ("$ansnumstr");
+ var need_bubble_count = 0;
+ for (var i=0; i 1) {
+ var bubble_picked = 0;
+ for (var j=0; j
+ENDSCRIPT
+ return $output;
+}
+
=pod
=item questions_to_line_list
@@ -6635,11 +6748,26 @@ sub questions_to_line_list {
my ($questions) = @_;
my @lines;
- foreach my $question (@{$questions}) {
- my $first = $first_bubble_line{$question-1} + 1;
- my $count = $bubble_lines_per_response{$question-1};
- my $last = $first+$count-1;
- push(@lines, ($first..$last));
+ foreach my $item (@{$questions}) {
+ my $question = $item;
+ my ($first,$count,$last);
+ if ($item =~ /^(\d+)\.(\d+)$/) {
+ $question = $1;
+ my $subquestion = $2;
+ $first = $first_bubble_line{$question-1} + 1;
+ my @subans = split(/,/,$subdivided_bubble_lines{$question-1});
+ my $subcount = 1;
+ while ($subcount<$subquestion) {
+ $first += $subans[$subcount-1];
+ $subcount ++;
+ }
+ $count = $subans[$subquestion-1];
+ } else {
+ $first = $first_bubble_line{$question-1} + 1;
+ $count = $bubble_lines_per_response{$question-1};
+ }
+ $last = $first+$count-1;
+ push(@lines, ($first..$last));
}
return join(',', @lines);
}
@@ -6657,33 +6785,70 @@ for multi and missing bubble cases).
$question - The question number to prompt for.
$scan_config - The scantron file configuration hash.
$scan_record - Reference to the hash that has the the parsed scanlines.
+ $error - Type of error
Implicit inputs:
%bubble_lines_per_response - Starting line numbers for each question.
Numbered from 0 (but question numbers are from
1.
%first_bubble_line - Starting bubble line for each question.
+ %subdivided_bubble_lines - optionresponse and matchresponse type
+ problems render as separate sub-questions,
+ in exam mode. This hash contains a
+ comma-separated list of the lines per
+ sub-question.
+ %responsetype_per_response - essayresponse, forumalaresponse, and
+ stringresponse type problem parts can have
+ multiple lines per response if the weight
+ assigned exceeds 10. In this case, only
+ one bubble per line is permitted, but more
+ than one line might contain bubbles, e.g.
+ bubbling of: line 1 - J, line 2 - J,
+ line 3 - B would assign 22 points.
=cut
sub prompt_for_corrections {
- my ($r, $question, $scan_config, $scan_record) = @_;
-
- my $lines = $bubble_lines_per_response{$question-1};
- my $current_line = $first_bubble_line{$question-1} + 1 ;
-
+ my ($r, $question, $scan_config, $scan_record, $error) = @_;
+ my ($current_line,$lines);
+ my @linenums;
+ my $questionnum = $question;
+ if ($question =~ /^(\d+)\.(\d+)$/) {
+ $question = $1;
+ $current_line = $first_bubble_line{$question-1} + 1 ;
+ my $subquestion = $2;
+ my @subans = split(/,/,$subdivided_bubble_lines{$question-1});
+ my $subcount = 1;
+ while ($subcount<$subquestion) {
+ $current_line += $subans[$subcount-1];
+ $subcount ++;
+ }
+ $lines = $subans[$subquestion-1];
+ } else {
+ $current_line = $first_bubble_line{$question-1} + 1 ;
+ $lines = $bubble_lines_per_response{$question-1};
+ }
if ($lines > 1) {
- $r->print(&mt("The group of bubble lines below responds to a single question. Select at most one bubble in a single line and select 'No Bubble' in all the other lines. ")." ");
+ $r->print(&mt('The group of bubble lines below responds to a single question.').' ');
+ if (($responsetype_per_response{$question-1} eq 'essayresponse') ||
+ ($responsetype_per_response{$question-1} eq 'formularesponse') ||
+ ($responsetype_per_response{$question-1} eq 'stringresponse')) {
+ $r->print(&mt("Although this particular question type requires handgrading, the instructions for this question in the exam directed students to leave [quant,_1,line] blank on their scantron sheets.",$lines).'
'.&mt('A non-zero score can be assigned to the student during scantron grading by selecting a bubble in at least one line.').' '.&mt('The score for this question will be a sum of the numeric values for the selected bubbles from each line, where A=1 point, B=2 points etc.').' '.&mt("To assign a score of zero for this question, mark all lines as 'No bubble'.").'
');
+ } else {
+ $r->print(&mt("Select at most one bubble in a single line and select 'No Bubble' in all the other lines. ")." ");
+ }
}
for (my $i =0; $i < $lines; $i++) {
- my $selected = $$scan_record{"scantron.$current_line.answer"};
- &scantron_bubble_selector($r, $scan_config, $current_line,
- split('', $selected));
+ my $selected = $$scan_record{"scantron.$current_line.answer"};
+ &scantron_bubble_selector($r,$scan_config,$current_line,
+ $questionnum,$error,split('', $selected));
+ push (@linenums,$current_line);
$current_line++;
}
if ($lines > 1) {
$r->print(" ");
}
+ return @linenums;
}
=pod
@@ -6697,34 +6862,46 @@ sub prompt_for_corrections {
$r - Apache request object
$scan_config - hash from &get_scantron_config()
$line - Number of the line being displayed.
+ $questionnum - Question number (may include subquestion)
+ $error - Type of error.
@selected - Array of bubbles picked on this line.
=cut
sub scantron_bubble_selector {
- my ($r,$scan_config,$line,@selected)=@_;
+ my ($r,$scan_config,$line,$questionnum,$error,@selected)=@_;
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("
');
+ $r->print(&Apache::loncommon::end_data_table_row().
+ &Apache::loncommon::end_data_table());
}
=pod
@@ -6904,7 +7081,6 @@ sub scantron_validate_doublebubble {
#get scantron line setup
my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
my ($scanlines,$scan_data)=&scantron_getfile();
-
&scantron_get_maxbubble(); # parse needs the bubble line array.
for (my $i=0;$i<=$scanlines->{'count'};$i++) {
@@ -6931,14 +7107,17 @@ sub scantron_validate_doublebubble {
for what the current value of the problem counter is.
Caches the results to $env{'form.scantron_maxbubble'},
- $env{'form.scantron.bubble_lines.n'} and
- $env{'form.scantron.first_bubble_line.n'}
+ $env{'form.scantron.bubble_lines.n'},
+ $env{'form.scantron.first_bubble_line.n'} and
+ $env{"form.scantron.sub_bubblelines.n"}
which are the total number of bubble, lines, the number of bubble
- lines for reponse n and number of the first bubble line for response n.
+ lines for response n and number of the first bubble line for response n,
+ and a comma separated list of numbers of bubble lines for sub-questions
+ (for optionresponse items only), for response n.
=cut
-sub scantron_get_maxbubble {
+sub scantron_get_maxbubble {
if (defined($env{'form.scantron_maxbubble'}) &&
$env{'form.scantron_maxbubble'}) {
&restore_bubble_lines();
@@ -6960,12 +7139,23 @@ sub scantron_get_maxbubble {
my $total_lines = 0;
%bubble_lines_per_response = ();
%first_bubble_line = ();
-
+ %subdivided_bubble_lines = ();
+ %responsetype_per_response = ();
my $response_number = 0;
my $bubble_line = 0;
foreach my $resource (@resources) {
- my $symb = $resource->symb();
+ # Need to retrieve part IDs and response IDs because essayresponse
+ # items are not included in $analysis{'parts'} from lonnet::ssi.
+ my %possible_part_ids;
+ if (ref($resource->parts()) eq 'ARRAY') {
+ foreach my $part (@{$resource->parts()}) {
+ my @resp_ids = $resource->responseIds($part);
+ foreach my $id (@resp_ids) {
+ $possible_part_ids{$part.'.'.$id} = 1;
+ }
+ }
+ }
my $result=&Apache::lonnet::ssi($resource->src(),
('symb' => $resource->symb()),
('grade_target' => 'analyze'),
@@ -6975,21 +7165,60 @@ sub scantron_get_maxbubble {
my (undef, $an) =
split(/_HASH_REF__/,$result, 2);
- my %analysis = &Apache::lonnet::str2hash($an);
-
+ my @parts;
+ my %analysis = &Apache::lonnet::str2hash($an);
- foreach my $part_id (@{$analysis{'parts'}}) {
-
- my $lines = $analysis{"$part_id.bubble_lines"};;
-
+ if (ref($analysis{'parts'}) eq 'ARRAY') {
+ @parts = @{$analysis{'parts'}};
+ }
+ # Add part_ids for any essayresponse items.
+ foreach my $part_id (keys(%possible_part_ids)) {
+ if ($analysis{$part_id.'.type'} eq 'essayresponse') {
+ if (!grep(/^\Q$part_id\E$/,@parts)) {
+ push (@parts,$part_id);
+ }
+ }
+ }
+ foreach my $part_id (@parts) {
+ my $lines = $analysis{"$part_id.bubble_lines"};
# TODO - make this a persistent hash not an array.
+ # optionresponse and matchresponse type items render as
+ # separate sub-questions in exam mode.
+ if (($analysis{$part_id.'.type'} eq 'optionresponse') ||
+ ($analysis{$part_id.'.type'} eq 'matchresponse')) {
+ my ($numbub,$numshown);
+ if ($analysis{$part_id.'.type'} eq 'optionresponse') {
+ if (ref($analysis{$part_id.'.options'}) eq 'ARRAY') {
+ $numbub = scalar(@{$analysis{$part_id.'.options'}});
+ }
+ } elsif ($analysis{$part_id.'.type'} eq 'matchresponse') {
+ if (ref($analysis{$part_id.'.items'}) eq 'ARRAY') {
+ $numbub = scalar(@{$analysis{$part_id.'.items'}});
+ }
+ }
+ if (ref($analysis{$part_id.'.shown'}) eq 'ARRAY') {
+ $numshown = scalar(@{$analysis{$part_id.'.shown'}});
+ }
+ my $bubbles_per_line = 10;
+ my $inner_bubble_lines = int($numshown/$bubbles_per_line);
+ if (($numshown % $bubbles_per_line) != 0) {
+ $inner_bubble_lines++;
+ }
+ for (my $i=0; $i<$numshown; $i++) {
+ $subdivided_bubble_lines{$response_number} .=
+ $inner_bubble_lines.',';
+ }
+ $subdivided_bubble_lines{$response_number} =~ s/,$//;
+ }
- $first_bubble_line{$response_number} = $bubble_line;
- $bubble_lines_per_response{$response_number} = $lines;
+ $first_bubble_line{$response_number} = $bubble_line;
+ $bubble_lines_per_response{$response_number} = $lines;
+ $responsetype_per_response{$response_number} =
+ $analysis{$part_id.'.type'};
$response_number++;
$bubble_line += $lines;
@@ -7037,7 +7266,25 @@ sub scantron_validate_missingbubbles {
# Probably here's where the error is...
foreach my $missing (@{$$scan_record{'scantron.missingerror'}}) {
- if ($missing > $max_bubble) { next; }
+ my $lastbubble;
+ if ($missing =~ /^(\d+)\.(\d+)$/) {
+ my $question = $1;
+ my $subquestion = $2;
+ if (!defined($first_bubble_line{$question -1})) { next; }
+ my $first = $first_bubble_line{$question-1};
+ my @subans = split(/,/,$subdivided_bubble_lines{$question-1});
+ my $subcount = 1;
+ while ($subcount<$subquestion) {
+ $first += $subans[$subcount-1];
+ $subcount ++;
+ }
+ my $count = $subans[$subquestion-1];
+ $lastbubble = $first + $count;
+ } else {
+ if (!defined($first_bubble_line{$missing - 1})) { next; }
+ $lastbubble = $first_bubble_line{$missing - 1} + $bubble_lines_per_response{$missing - 1};
+ }
+ if ($lastbubble > $max_bubble) { next; }
push(@to_correct,$missing);
}
if (@to_correct) {
@@ -7956,8 +8203,6 @@ ENDHEADER
}
$result.=' '.&mt('Found [_1] question(s)',$number).' '.
''.
- &mt('Awarding [_1] percent for corrion(s)',$number).' '.
- ''.
&mt('Awarding [_1] percent for correct and [_2] percent for incorrect responses',
$env{'form.pcorrect'},$env{'form.pincorrect'}).
' ';