--- loncom/homework/caparesponse/caparesponse.pm 2006/03/09 00:41:13 1.188
+++ loncom/homework/caparesponse/caparesponse.pm 2007/03/01 02:31:29 1.209
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# caparesponse definition
#
-# $Id: caparesponse.pm,v 1.188 2006/03/09 00:41:13 albertel Exp $
+# $Id: caparesponse.pm,v 1.209 2007/03/01 02:31:29 albertel Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -29,15 +29,26 @@
package Apache::caparesponse;
use strict;
use capa;
+use Safe::Hole;
+use Apache::lonmaxima();
use Apache::lonlocal;
use Apache::lonnet;
+use Apache::response();
+use Storable qw(dclone);
BEGIN {
- &Apache::lonxml::register('Apache::caparesponse',('caparesponse','numericalresponse','stringresponse','formularesponse'));
+ &Apache::lonxml::register('Apache::caparesponse',('numericalresponse','stringresponse','formularesponse'));
}
my %answer;
+my @answers;
+sub get_answer { return %answer; };
+sub push_answer{ push(@answers,dclone(\%answer)); undef(%answer) }
+sub pop_answer { %answer = %{pop(@answers)}; };
+
my $cur_name;
+my $tag_internal_answer_name = 'INTERNAL';
+
sub start_answer {
my ($target,$token,$tagstack,$parstack,$parser,$safeeval)=@_;
my $result;
@@ -50,31 +61,92 @@ sub start_answer {
if (!defined($type)) { $type = 'ordered' };
$answer{$cur_name}= { 'type' => $type,
'answers' => [] };
+ if ($target eq 'edit') {
+ $result.=&Apache::edit::tag_start($target,$token);
+ $result.=&Apache::edit::text_arg('Name:','name',$token);
+ $result.=&Apache::edit::select_arg('Type:','type',
+ [['ordered', 'Ordered' ],
+ ['unordered','Unordered'],],
+ $token);
+ $result.=&Apache::edit::end_row().&Apache::edit::start_spanning_row();
+ } elsif ($target eq 'modified') {
+ my $constructtag = &Apache::edit::get_new_args($token,$parstack,
+ $safeeval,'name',
+ 'type');
+ if ($constructtag) {
+ $result = &Apache::edit::rebuild_tag($token);
+ $result.= &Apache::edit::handle_insert();
+ }
+ }
return $result;
}
sub end_answer {
my ($target,$token,$tagstack,$parstack,$parser,$safeeval)=@_;
my $result;
+ if ($target eq 'edit') {
+ $result .= &Apache::edit::tag_end();
+ }
+
undef($cur_name);
return $result;
}
+sub insert_answer {
+ return '
+
");
if ( &Apache::response::submitted('scantron')) {
+ &add_in_tag_answer($parstack,$safeeval);
my ($values,$display)=&make_numerical_bubbles($partid,$id,
$target,$parstack,$safeeval);
$response=$values->[$response];
}
$Apache::lonhomework::results{"resource.$partid.$id.submission"}=$response;
- my ($ad,$msg)=&check_submission($response,$partid,$id,
- $tag,$parstack,$safeeval);
+ my ($ad,$msg,$name)=&check_submission($response,$partid,$id,
+ $tag,$parstack,
+ $safeeval);
&Apache::lonxml::debug('ad is'.$ad);
if ($ad eq 'SIG_FAIL') {
@@ -330,15 +536,17 @@ sub end_numericalresponse {
&Apache::response::handle_previous(\%previous,$ad);
$Apache::lonhomework::results{"resource.$partid.$id.awarddetail"}=$ad;
$Apache::lonhomework::results{"resource.$partid.$id.awardmsg"}=$msg;
+ $Apache::lonhomework::results{"resource.$partid.$id.answername"}=$name;
$result='';
}
}
} elsif ($target eq 'web' || $target eq 'tex') {
- my (@answers)=&Apache::lonxml::get_param_var('answer',$parstack,
- $safeeval);
+ &check_for_answer_errors($parstack,$safeeval);
my $award = $Apache::lonhomework::history{"resource.$Apache::inputtags::part.solved"};
my $status = $Apache::inputtags::status['-1'];
if ($Apache::lonhomework::type eq 'exam') {
+ # FIXME support multi dimensional numerical problems
+ # in exam bubbles
my ($bubble_values,$bubble_display)=
&make_numerical_bubbles($partid,$id,$target,$parstack,
$safeeval);
@@ -393,6 +601,7 @@ sub end_numericalresponse {
}
}
}
+ &setup_prior_tries_hash();
} elsif ($target eq 'edit') {
$result.=''.&Apache::edit::end_table;
} elsif ($target eq 'answer' || $target eq 'analyze') {
@@ -409,13 +618,14 @@ sub end_numericalresponse {
if (scalar(@$tagstack)) {
&Apache::response::setup_params($tag,$safeeval);
}
- my (@answers)=&Apache::lonxml::get_param_var('answer',$parstack,$safeeval);
+ &add_in_tag_answer($parstack,$safeeval);
my (@formats)=&Apache::lonxml::get_param_var('format',$parstack,$safeeval);
+
my $unit=&Apache::lonxml::get_param_var('unit',$parstack,$safeeval);
- my $type=&Apache::lonxml::get_param('type',$parstack,$safeeval);
if ($target eq 'answer') {
- $result.=&Apache::response::answer_header($tag);
+ $result.=&Apache::response::answer_header($tag,undef,
+ scalar(keys(%answer)));
if ($tag eq 'numericalresponse'
&& $Apache::lonhomework::type eq 'exam') {
my ($bubble_values,undef,$correct) = &make_numerical_bubbles($partid,
@@ -423,121 +633,152 @@ sub end_numericalresponse {
$result.=&Apache::response::answer_part($tag,$correct);
}
}
- my ($sigline,$tolline);
- for(my $i=0;$i<=$#answers;$i++) {
- my $ans=$answers[$i];
- my $fmt=$formats[0];
- if (@formats && $#formats) {$fmt=$formats[$i];}
- my ($high,$low);
- if ($Apache::inputtags::params{'tol'}) {
- ($high,$low)=&get_tolrange($ans,$Apache::inputtags::params{'tol'});
- }
- my ($sighigh,$siglow);
- if ($Apache::inputtags::params{'sig'}) {
- ($sighigh,$siglow)=&get_sigrange($Apache::inputtags::params{'sig'});
- }
- if ($fmt && $tag eq 'numericalresponse') {
- $fmt=~s/e/E/g;
- if ($unit=~/\$/) { $fmt="\$".$fmt; $unit=~s/\$//g; }
- if ($unit=~/\,/) { $fmt="\,".$fmt; $unit=~s/\,//g; }
- $ans = &format_number($ans,$fmt,$target,$safeeval);
- #if ($high) {
- # $high=&format_number($high,$fmt,$target,$safeeval);
- # $low =&format_number($low,$fmt,$target,$safeeval);
- #}
- }
- if ($target eq 'answer') {
- if ($high && $tag eq 'numericalresponse') {
- $ans.=' ['.$low.','.$high.']';
- $tolline .= "[$low, $high]";
+ foreach my $name (sort(keys(%answer))) {
+ my @answers = @{ $answer{$name}{'answers'} };
+ if ($target eq 'analyze') {
+ foreach my $info ('answer','ans_high','ans_low','format') {
+ $Apache::lonhomework::analyze{"$part_id.$info"}{$name}=[];
}
- if (defined($sighigh) && $tag eq 'numericalresponse') {
- if ($env{'form.answer_output_mode'} eq 'tex') {
- $ans.= " Sig $siglow - $sighigh";
- } else {
- $ans.= " Sig $siglow - $sighigh";
- $sigline .= "[$siglow, $sighigh]";
+ }
+ my ($sigline,$tolline);
+ if ($name ne $tag_internal_answer_name
+ || scalar(keys(%answer)) > 1) {
+ $result.=&Apache::response::answer_part($tag,$name);
+ }
+ for(my $i=0;$i<=$#answers;$i++) {
+ my $ans=$answers[$i];
+ my $fmt=$formats[0];
+ if (@formats && $#formats) {$fmt=$formats[$i];}
+ my ($sighigh,$siglow);
+ if ($Apache::inputtags::params{'sig'}) {
+ ($sighigh,$siglow)=&get_sigrange($Apache::inputtags::params{'sig'});
+ }
+ my @vector;
+ if (ref($ans)) {
+ @vector = @{ $ans };
+ } else {
+ @vector = ($ans);
+ }
+ my @all_answer_info;
+ foreach my $element (@vector) {
+ my ($high,$low);
+ if ($Apache::inputtags::params{'tol'}) {
+ ($high,$low)=&get_tolrange($element,$Apache::inputtags::params{'tol'});
+ }
+ if ($target eq 'answer') {
+ if ($fmt && $tag eq 'numericalresponse') {
+ $fmt=~s/e/E/g;
+ if ($unit=~/\$/) { $fmt="\$".$fmt; $unit=~s/\$//g; }
+ if ($unit=~/\,/) { $fmt="\,".$fmt; $unit=~s/\,//g; }
+ $element = &format_number($element,$fmt,$target,$safeeval);
+ #if ($high) {
+ # $high=&format_number($high,$fmt,$target,$safeeval);
+ # $low =&format_number($low,$fmt,$target,$safeeval);
+ #}
+ }
+ if ($high && $tag eq 'numericalresponse') {
+ $element.=' ['.$low.','.$high.']';
+ $tolline .= "[$low, $high]";
+ }
+ if (defined($sighigh) && $tag eq 'numericalresponse') {
+ if ($env{'form.answer_output_mode'} eq 'tex') {
+ $element.= " Sig $siglow - $sighigh";
+ } else {
+ $element.= " Sig $siglow - $sighigh";
+ $sigline .= "[$siglow, $sighigh]";
+ }
+ }
+ push(@all_answer_info,$element);
+
+ } elsif ($target eq 'analyze') {
+ push (@{ $Apache::lonhomework::analyze{"$part_id.answer"}{$name}[$i] }, $element);
+ if ($high) {
+ push (@{ $Apache::lonhomework::analyze{"$part_id.ans_high"}{$name}[$i] }, $high);
+ push (@{ $Apache::lonhomework::analyze{"$part_id.ans_low"}{$name}[$i] }, $low);
+ }
+ if ($fmt) {
+ push (@{ $Apache::lonhomework::analyze{"$part_id.format"}{$name}[$i] }, $fmt);
+ }
}
}
- $result.=&Apache::response::answer_part($tag,$ans);
- } elsif ($target eq 'analyze') {
- push (@{ $Apache::lonhomework::analyze{"$part_id.answer"} }, $ans);
- if ($high) {
- push (@{ $Apache::lonhomework::analyze{"$part_id.ans_high"} }, $high);
- push (@{ $Apache::lonhomework::analyze{"$part_id.ans_low"} }, $low);
- }
- if ($fmt) {
- push (@{ $Apache::lonhomework::analyze{"$part_id.format"} }, $fmt);
+ if ($target eq 'answer') {
+ $result.= &Apache::response::answer_part($tag,join(', ',@all_answer_info));
}
}
- }
- my @fmt_ans;
- for(my $i=0;$i<=$#answers;$i++) {
- my $ans=$answers[$i];
- my $fmt=$formats[0];
- if (@formats && $#formats) {$fmt=$formats[$i];}
- if ($fmt && $tag eq 'numericalresponse') {
- $fmt=~s/e/E/g;
- if ($unit=~/\$/) { $fmt="\$".$fmt; $unit=~s/\$//g; }
- if ($unit=~/\,/) { $fmt="\,".$fmt; $unit=~s/\,//g; }
- $ans = &format_number($ans,$fmt,$target,$safeeval);
- if ($fmt=~/\$/ && $unit!~/\$/) { $ans=~s/\$//; }
- }
- push(@fmt_ans,$ans);
- }
- my $response=join(', ',@fmt_ans);
- my $hideunit=&Apache::lonnet::EXT('resource.'.$partid.'_'.
- $id.'.turnoffunit');
- if ($unit ne '' &&
- ! ($Apache::lonhomework::type eq 'exam' ||
- lc($hideunit) eq "yes") ) {
- my $cleanunit=$unit;
- $cleanunit=~s/\$\,//g;
- $response.=" $cleanunit";
- }
-
- my ($ad,$msg)=&check_submission($response,$partid,$id,$tag,
- $parstack,$safeeval);
- if ($ad ne 'EXACT_ANS' && $ad ne 'APPROX_ANS') {
- my $error;
- if ($tag eq 'formularesponse') {
- $error=&mt('Computer\'s answer is incorrect ("[_1]").',$response);
- } else {
- # answer failed check if it is sig figs that is failing
- my ($ad,$msg)=&check_submission($response,$partid,$id,
- $tag,$parstack,
- $safeeval,1);
- if ($sigline ne '') {
- $error=&mt('Computer\'s answer is incorrect ("[_1]"). It is likely that the tolerance range [_2] or significant figures [_3] need to be adjusted.',$response,$tolline,$sigline);
- } else {
- $error=&mt('Computer\'s answer is incorrect ("[_1]"). It is likely that the tolerance range [_2] needs to be adjusted.',$response,$tolline);
+ my @fmt_ans;
+ for(my $i=0;$i<=$#answers;$i++) {
+ my $ans=$answers[$i];
+ my $fmt=$formats[0];
+ if (@formats && $#formats) {$fmt=$formats[$i];}
+ foreach my $element (@$ans) {
+ if ($fmt && $tag eq 'numericalresponse') {
+ $fmt=~s/e/E/g;
+ if ($unit=~/\$/) { $fmt="\$".$fmt; $unit=~s/\$//g; }
+ if ($unit=~/\,/) { $fmt="\,".$fmt; $unit=~s/\,//g; }
+ $element = &format_number($element,$fmt,$target,
+ $safeeval);
+ if ($fmt=~/\$/ && $unit!~/\$/) { $element=~s/\$//; }
+ }
+ }
+ push(@fmt_ans,join(',',@$ans));
+ }
+ my $response=\@fmt_ans;
+
+ my $hideunit=&Apache::lonnet::EXT('resource.'.$partid.'_'.
+ $id.'.turnoffunit');
+ if ($unit ne '' &&
+ ! ($Apache::lonhomework::type eq 'exam' ||
+ lc($hideunit) eq "yes") ) {
+ my $cleanunit=$unit;
+ $cleanunit=~s/\$\,//g;
+ foreach my $ans (@fmt_ans) {
+ $ans.=" $cleanunit";
}
}
+ my ($ad,$msg)=&check_submission($response,$partid,$id,$tag,
+ $parstack,$safeeval);
if ($ad ne 'EXACT_ANS' && $ad ne 'APPROX_ANS') {
- &Apache::lonxml::error($error);
- } else {
- &Apache::lonxml::warning($error);
+ my $error;
+ if ($tag eq 'formularesponse') {
+ $error=&mt('Computer\'s answer is incorrect ("[_1]").',join(', ',@$response));
+ } else {
+ # answer failed check if it is sig figs that is failing
+ my ($ad,$msg)=&check_submission($response,$partid,$id,
+ $tag,$parstack,
+ $safeeval,1);
+ if ($sigline ne '') {
+ $error=&mt('Computer\'s answer is incorrect ("[_1]"). It is likely that the tolerance range [_2] or significant figures [_3] need to be adjusted.',join(', ',@$response),$tolline,$sigline);
+ } else {
+ $error=&mt('Computer\'s answer is incorrect ("[_1]"). It is likely that the tolerance range [_2] needs to be adjusted.',join(', ',@$response),$tolline);
+ }
+ }
+ if ($ad ne 'EXACT_ANS' && $ad ne 'APPROX_ANS') {
+ &Apache::lonxml::error($error);
+ } else {
+ &Apache::lonxml::warning($error);
+ }
}
- }
- if (defined($unit) and ($unit ne '') and
- $tag eq 'numericalresponse') {
- if ($target eq 'answer') {
- if ($env{'form.answer_output_mode'} eq 'tex') {
- $result.=&Apache::response::answer_part($tag,
- " Unit: $unit ");
- } else {
- $result.=&Apache::response::answer_part($tag,
- "Unit: $unit");
+ if (defined($unit) and ($unit ne '') and
+ $tag eq 'numericalresponse') {
+ if ($target eq 'answer') {
+ if ($env{'form.answer_output_mode'} eq 'tex') {
+ $result.=&Apache::response::answer_part($tag,
+ " Unit: $unit ");
+ } else {
+ $result.=&Apache::response::answer_part($tag,
+ "Unit: $unit");
+ }
+ } elsif ($target eq 'analyze') {
+ push (@{ $Apache::lonhomework::analyze{"$part_id.unit"} }, $unit);
}
- } elsif ($target eq 'analyze') {
- push (@{ $Apache::lonhomework::analyze{"$part_id.unit"} }, $unit);
}
- }
- if ($tag eq 'formularesponse' && $target eq 'answer') {
- my $samples=&Apache::lonxml::get_param('samples',$parstack,$safeeval);
- $result.=&Apache::response::answer_part($tag,$samples);
+ if ($tag eq 'formularesponse' && $target eq 'answer') {
+ my $samples=&Apache::lonxml::get_param('samples',$parstack,$safeeval);
+ $result.=&Apache::response::answer_part($tag,$samples);
+ }
+ $result.=&Apache::response::next_answer($tag,$name);
}
if ($target eq 'answer') {
$result.=&Apache::response::answer_footer($tag);
@@ -547,10 +788,49 @@ sub end_numericalresponse {
$target eq 'tex' || $target eq 'analyze') {
&Apache::lonxml::increment_counter($increment);
}
- &Apache::response::end_response;
+ &Apache::response::end_response();
return $result;
}
+sub setup_prior_tries_hash {
+ #FIXME support multi answer numericals/formula
+ my $part=$Apache::inputtags::part;
+ my $id=$Apache::inputtags::response[-1];
+ foreach my $i (1..$Apache::lonhomework::history{'version'}) {
+ my $key = "$i:resource.$part.$id.submission";
+ next if (!exists($Apache::lonhomework::history{"$key"}));
+ $Apache::inputtags::submission_display{$key} =
+ '
'.&HTML::Entities::encode($Apache::lonhomework::history{$key}, + '"<>&').''; + } +} + +sub check_for_answer_errors { + my ($parstack,$safeeval) = @_; + &add_in_tag_answer($parstack,$safeeval); + my %counts; + foreach my $name (keys(%answer)) { + push(@{$counts{scalar(@{$answer{$name}{'answers'}})}},$name); + } + if (scalar(keys(%counts)) > 1) { + my $counts = join(' ',map { + my $count = $_; + &mt("Answers [_1] had [_2] components.", + ''.join(', ',@{$counts{$count}}).'', + $count); + } (sort(keys(%counts)))); + &Apache::lonxml::error(&mt("All answers must have the same number of components. Varying numbers of answers were seen. ").$counts); + } + use Data::Dumper; + &Apache::lonxml::debug("count dump is ".&Dumper(\%counts)); + my $expected_number_of_inputs = (keys(%counts))[0]; + if ( $expected_number_of_inputs != scalar(@Apache::inputtags::inputlist)) { + &Apache::lonxml::error(&mt("Expected [_1] input fields, but there were only [_2] seen.", + $expected_number_of_inputs, + scalar(@Apache::inputtags::inputlist))); + } +} + sub get_table_sizes { my ($number_of_bubbles,$rbubble_values)=@_; my $scale=2; #mm for one digit @@ -620,7 +900,18 @@ sub make_numerical_bubbles { &Apache::response::get_response_param($part.'_'.$id,'numbubbles',8); my ($format)=&Apache::lonxml::get_param_var('format',$parstack,$safeeval); - my ($answer)=&Apache::lonxml::get_param_var('answer',$parstack,$safeeval); + my $name = (exists($answer{$tag_internal_answer_name}) + ? $tag_internal_answer_name + : (sort(keys(%answer)))[0]); + + if ( scalar(@{$answer{$name}{'answers'}}) > 1) { + &Apache::lonxml::error("Only answers with 1 component are supported in exam mode"); + } + if (scalar(@{$answer{$name}{'answers'}[0]}) > 1) { + &Apache::lonxml::error("Vector answers are unsupported in exam mode."); + } + + my $answer = $answer{$name}{'answers'}[0][0]; my (@incorrect)=&Apache::lonxml::get_param_var('incorrect',$parstack, $safeeval); if ($#incorrect eq 0) { @incorrect=(split(/,/,$incorrect[0])); } @@ -702,7 +993,7 @@ sub get_tolrange { sub get_sigrange { my ($sig)=@_; - &Apache::lonxml::debug("Got a sig of :$sig:"); + #&Apache::lonxml::debug("Got a sig of :$sig:"); my $courseid=$env{'request.course.id'}; if (lc($env{"course.$courseid.disablesigfigs"}) eq 'yes') { return (15,0); @@ -766,7 +1057,7 @@ sub start_stringresponse { $answer= &Apache::lonxml::get_param('answer',$parstack,$safeeval); } - $Apache::inputtags::answertxt{$id}=$answer; + $Apache::inputtags::answertxt{$id}=[$answer]; } } elsif ($target eq 'answer' || $target eq 'grade') { &Apache::response::reset_params(); @@ -776,7 +1067,7 @@ sub start_stringresponse { sub end_stringresponse { my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; - my $increment=1; + my $result = ''; my $part=$Apache::inputtags::part; my $id=$Apache::inputtags::response[-1]; @@ -792,7 +1083,8 @@ sub end_stringresponse { $safeeval->share_from('capa',['&caparesponse_capa_check_answer']); if ($Apache::lonhomework::type eq 'exam' || &Apache::response::submitted('scantron')) { - $increment=&Apache::response::scored_response($part,$id); + &Apache::response::scored_response($part,$id); + } else { my $response = &Apache::response::getresponse(); if ( $response =~ /[^\s]/) { @@ -816,24 +1108,30 @@ sub end_stringresponse { &Apache::lonxml::debug("current $answer"); $ad = ($result) ? 'APPROX_ANS' : 'INCORRECT'; } else { - my $args_ref= - \%{$safeeval->varglob('LONCAPA::CAPAresponse_args')}; - - $$args_ref{'response'}=$response; - &Apache::lonxml::debug("current $response"); - $$args_ref{'type'}= - &Apache::lonxml::get_param('type',$parstack,$safeeval); - foreach my $key (keys(%Apache::inputtags::params)) { - $$args_ref{$key}=$Apache::inputtags::params{$key}; + my @args = ('type'); + my $args_ref = &setup_capa_args($safeeval,$parstack, + \@args,$response); + + &add_in_tag_answer($parstack,$safeeval); + my (@final_awards,@final_msgs,@names); + foreach my $name (keys(%answer)) { + &Apache::lonxml::debug(" doing $name with ".join(':',@{ $answer{$name}{'answers'} })); + ${$safeeval->varglob('LONCAPA::CAPAresponse_answer')}=dclone($answer{$name}); + my ($result, @msgs)=&Apache::run::run("&caparesponse_check_list()",$safeeval); + &Apache::lonxml::debug('msgs are'.join(':',@msgs)); + my ($awards)=split(/:/,$result); + my (@awards) = split(/,/,$awards); + ($ad,$msg) = + &Apache::inputtags::finalizeawards(\@awards,\@msgs); + push(@final_awards,$ad); + push(@final_msgs,$msg); + push(@names,$name); + &Apache::lonxml::debug("\n