File:  [LON-CAPA] / loncom / homework / caparesponse / caparesponse.pm
Revision 1.253: download - view: text, annotated - select for diffs
Fri Dec 14 11:17:09 2012 UTC (11 years, 4 months ago) by foxr
Branches: MAIN
CVS tags: HEAD
BZ 6577 - Ensure that computed answers all will have an appropriate number of
significant figures to pass the validation test...well actualy all user answers
get extended to the appropriate number of sigfigs so that answers will never
fail due to significance.
  Students will still need to force answers into a form that has the
correct number of sigfigs (e.g for 3 sigfigs not 300 but 3.00e2)..

# The LearningOnline Network with CAPA
# caparesponse definition
#
# $Id: caparesponse.pm,v 1.249.8.2 2012/02/04 20:40:15 foxr 
#
# Copyright Michigan State University Board of Trustees
#
# This file is part of the LearningOnline Network with CAPA (LON-CAPA).
#
# LON-CAPA is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# LON-CAPA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with LON-CAPA; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# /home/httpd/html/adm/gpl.txt
#
# http://www.lon-capa.org/
#

package Apache::caparesponse;
use strict;
use capa;
use Safe::Hole;
use Apache::lonmaxima();
use Apache::lonlocal;
use Apache::lonnet;
use Apache::lonmsg();
use Apache::response();
use Storable qw(dclone);
use Apache::lonnet;

BEGIN {
    &Apache::lonxml::register('Apache::caparesponse',('numericalresponse','stringresponse','formularesponse'));
}

my %answer;
my @answers;
my @alphabet=('A'..'Z');

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;
    $cur_name = &Apache::lonxml::get_param('name',$parstack,$safeeval);
    if ($cur_name =~ /^\s*$/) { $cur_name = $Apache::lonxml::curdepth; }
    my $type = &Apache::lonxml::get_param('type',$parstack,$safeeval);
    if (!defined($type) && $tagstack->[-2] eq 'answergroup') {
	$type = &Apache::lonxml::get_param('type',$parstack,$safeeval,-2);
    }
    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 '
        <answer>
            <value></value>
        </answer>';
}

sub start_answergroup {
    my ($target,$token,$tagstack,$parstack,$parser,$safeeval)=@_;
    my $result;
    if ($target eq 'edit') {
  	$result.=&Apache::edit::tag_start($target,$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_answergroup {
    my ($target,$token,$tagstack,$parstack,$parser,$safeeval)=@_;
    my $result;
    if ($target eq 'web') {
    	if (  &Apache::response::show_answer() ) {  
	    my $partid = $Apache::inputtags::part;
	    my $id = $Apache::inputtags::response[-1];
	    &set_answertext($Apache::lonhomework::history{"resource.$partid.$id.answername"},
			    $target,$token,$tagstack,$parstack,$parser,
			    $safeeval,-2);
	}
    } elsif ($target eq 'edit') {
	$result .= &Apache::edit::tag_end();
    }
    return $result;
}

sub insert_answergroup {
    return '
    <answergroup>
        <answer>
            <value></value>
        </answer>
    </answergroup>';
}

sub start_value {
    my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
    my $result;
    if ( $target eq 'web' || $target eq 'tex' ||
	 $target eq 'grade' || $target eq 'webgrade' ||
	 $target eq 'answer' || $target eq 'analyze' ) {
	my $bodytext = &Apache::lonxml::get_all_text("/value",$parser,$style);
	$bodytext = &Apache::run::evaluate($bodytext,$safeeval,
					   $$parstack[-1]);
	
	push(@{ $answer{$cur_name}{'answers'} },[$bodytext]);
	
    } elsif ($target eq 'edit') {
  	$result.=&Apache::edit::tag_start($target,$token);
	my $bodytext = &Apache::lonxml::get_all_text("/value",$parser,$style);
	$result.=&Apache::edit::editline($token->[1],$bodytext,undef,40).
	    &Apache::edit::end_row();
    } elsif ($target eq 'modified') {
	$result=$token->[4].&Apache::edit::modifiedfield('/value',$parser);
    }
    return $result;
}

sub end_value {
    my ($target,$token,$tagstack,$parstack,$parser,$safeeval)=@_;
    my $result;
    if ($target eq 'edit') {
	$result = &Apache::edit::end_table();
    }
    return $result;
}

sub insert_value {
    return '
            <value></value>';
}

sub start_vector {
    my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
    my $result;
    if ( $target eq 'web' || $target eq 'tex' ||
	 $target eq 'grade' || $target eq 'webgrade' ||
	 $target eq 'answer' || $target eq 'analyze' ) {
	my $bodytext = &Apache::lonxml::get_all_text("/vector",$parser,$style);
	my @values = &Apache::run::run($bodytext,$safeeval,$$parstack[-1]);
	if (@values == 1) {
	    @values = split(',',$values[0]);
	}
	push(@{ $answer{$cur_name}{'answers'} },\@values);
    } elsif ($target eq 'edit') {
  	$result.=&Apache::edit::tag_start($target,$token);
	my $bodytext = &Apache::lonxml::get_all_text("/vector",$parser,$style);
	$result.=&Apache::edit::editline($token->[1],$bodytext,undef,40).
	    &Apache::edit::end_row();
    } elsif ($target eq 'modified') {
	$result=$token->[4].&Apache::edit::modifiedfield('/vector',$parser);
    }
    return $result;
}

sub end_vector {
    my ($target,$token,$tagstack,$parstack,$parser,$safeeval)=@_;
    my $result;
    if ($target eq 'edit') {
	$result = &Apache::edit::end_table();
    }
    return $result;
}

sub insert_vector {
    return '
            <value></value>';
}

sub start_array {
    my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
    my $result;
    if ( $target eq 'web' || $target eq 'tex' ||
	 $target eq 'grade' || $target eq 'webgrade' ||
	 $target eq 'answer' || $target eq 'analyze' ) {
	my $bodytext = &Apache::lonxml::get_all_text("/array",$parser,$style);
	my @values = &Apache::run::evaluate($bodytext,$safeeval,
					    $$parstack[-1]);
	push(@{ $answer{$cur_name}{'answers'} },@values);
    }
    return $result;
}

sub end_array {
    my ($target,$token,$tagstack,$parstack,$parser,$safeeval)=@_;
    my $result;
    return $result;
}

sub start_unit {
    my ($target,$token,$tagstack,$parstack,$parser,$safeeval)=@_;
    my $result;
    return $result;
}

sub end_unit {
    my ($target,$token,$tagstack,$parstack,$parser,$safeeval)=@_;
    my $result;
    return $result;
}

sub start_numericalresponse {
    my ($target,$token,$tagstack,$parstack,$parser,$safeeval)=@_;
    &Apache::lonxml::register('Apache::caparesponse',
			      ('answer','answergroup','value','array','unit',
			       'vector'));
    push(@Apache::lonxml::namespace,'caparesponse');
    my $id = &Apache::response::start_response($parstack,$safeeval);
    my $result;
    undef(%answer);
    undef(%{$safeeval->varglob('LONCAPA::CAPAresponse_args')});
    if ($target eq 'edit') {
	$result.=&Apache::edit::tag_start($target,$token);
	$result.=&Apache::edit::text_arg('Answer:','answer',$token);
	if ($token->[1] eq 'numericalresponse') {
	    $result.=&Apache::edit::text_arg('Incorrect Answers:','incorrect',
					     $token).
		&Apache::loncommon::help_open_topic('numerical_wrong_answers');
	    $result.=&Apache::edit::text_arg('Unit:','unit',$token,5).
		&Apache::loncommon::help_open_topic('Physical_Units');
	    $result.=&Apache::edit::text_arg('Format:','format',$token,4).
		&Apache::loncommon::help_open_topic('Numerical_Response_Format');
	} elsif ($token->[1] eq 'formularesponse') {
	    $result.=&Apache::edit::text_arg('Sample Points:','samples',
					     $token,40).
	      &Apache::loncommon::help_open_topic('Formula_Response_Sampling');
	}
        $result.=&Apache::edit::text_arg('Pre-Processor Subroutine:','preprocess',
                                             $token,10);
	$result.=&Apache::edit::end_row().&Apache::edit::start_spanning_row();
    } elsif ($target eq 'modified') {
	my $constructtag;
	if ($token->[1] eq 'numericalresponse') {
	    $constructtag=&Apache::edit::get_new_args($token,$parstack,
						      $safeeval,'answer',
 						      'incorrect','unit',
						      'format','preprocess');
	} elsif ($token->[1] eq 'formularesponse') {
	    $constructtag=&Apache::edit::get_new_args($token,$parstack,
						      $safeeval,'answer',
						      'samples','preprocess');
	}
	if ($constructtag) {
	    $result = &Apache::edit::rebuild_tag($token);
	    $result.=&Apache::edit::handle_insert();
	}
    } elsif ($target eq 'meta') {
	$result=&Apache::response::meta_package_write('numericalresponse');
    } elsif ($target eq 'answer' || $target eq 'grade') {
	&Apache::response::reset_params();
    } elsif ($target eq 'web') {
	my $partid = $Apache::inputtags::part;
	my $hideunit=&Apache::lonnet::EXT('resource.'.$partid.'_'.$id.'.turnoffunit');
	&Apache::lonxml::debug("Got unit $hideunit for $partid $id");
	#no way to enter units, with radio buttons
	if ((lc($hideunit) eq "yes") && ($Apache::lonhomework::type ne 'exam')) {
	    my $unit=&Apache::lonxml::get_param_var('unit',$parstack,
						    $safeeval);
	    if ($unit =~ /\S/) { $result.=" (in $unit) "; }
	}
	if (  &Apache::response::show_answer() ) {
	    &set_answertext($tag_internal_answer_name,$target,$token,$tagstack,
			    $parstack,$parser,$safeeval,-1);
	}
    }
    return $result;
}

sub set_answertext {
    my ($name,$target,$token,$tagstack,$parstack,$parser,$safeeval,
	$response_level) = @_;
    &add_in_tag_answer($parstack,$safeeval,$response_level);

    if ($name eq '' || !ref($answer{$name})) {
	if (ref($answer{$tag_internal_answer_name})) {
	    $name = $tag_internal_answer_name;
	} else {
	    $name = (sort(keys(%answer)))[0];
	}
    }
    return if ($name eq '' || !ref($answer{$name}));

    my (@formats)=&Apache::lonxml::get_param_var('format',$parstack,
						 $safeeval,$response_level);
    my $unit=&Apache::lonxml::get_param_var('unit',$parstack,$safeeval,
					    $response_level);

    &Apache::lonxml::debug("answer looks to be $name");
    my @answertxt;
    for (my $i=0; $i < scalar(@{$answer{$name}{'answers'}}); $i++) {
	my $answertxt;
	my $answer=$answer{$name}{'answers'}[$i];
	foreach my $element (@$answer) {
	    if ( scalar(@$tagstack)
		 && $tagstack->[$response_level] ne 'numericalresponse') {
		$answertxt.=$element.',';
	    } else {
		my $format;
		if ($#formats > 0) {
		    $format=$formats[$i];
		} else {
		    $format=$formats[0];
		}
		if ($unit=~/\$/) { $format="\$".$format; $unit=~s/\$//g; }
		if ($unit=~/\,/) { $format="\,".$format; $unit=~s/\,//g; }
		my $formatted=&format_number($element,$format,$target,
					     $safeeval);
		$answertxt.=' '.$formatted.',';
	    }
	    
	}
	chop($answertxt);
	if ($target eq 'web') {
	    $answertxt.=" $unit ";
	}

	push(@answertxt,$answertxt)
    }
	
    my $id = $Apache::inputtags::response[-1];
    $Apache::inputtags::answertxt{$id}=\@answertxt;
}

sub setup_capa_args {
    my ($safeeval,$parstack,$args,$response) = @_;
    my $args_ref= \%{$safeeval->varglob('LONCAPA::CAPAresponse_args')};
    undef(%{ $args_ref });
 
    foreach my $arg (@{$args}) {
	$$args_ref{$arg}=
	    &Apache::lonxml::get_param($arg,$parstack,$safeeval);
    }
    foreach my $key (keys(%Apache::inputtags::params)) {
	$$args_ref{$key}=$Apache::inputtags::params{$key};
    }
    &setup_capa_response($args_ref,$response);
    return $args_ref;
}

sub setup_capa_response {
    my ($args_ref,$response) = @_;   

    if (ref($response)) {
	$$args_ref{'response'}=dclone($response);
    } else {
	$$args_ref{'response'}=dclone([$response]);
    }
}

sub check_submission {
    my ($response,$partid,$id,$tag,$parstack,$safeeval,$ignore_sig)=@_;
    my @args = ('type','tol','sig','format','unit','calc','samples','preprocess');
    my $args_ref = &setup_capa_args($safeeval,$parstack,\@args,$response);

    my $hideunit=
	&Apache::lonnet::EXT('resource.'.$partid.'_'.$id.'.turnoffunit');
        #no way to enter units, with radio buttons
    if ($Apache::lonhomework::type eq 'exam' ||
	lc($hideunit) eq "yes") {
	delete($$args_ref{'unit'});
    }
    #sig fig don't make much sense either
    if (($Apache::lonhomework::type eq 'exam' ||
	 &Apache::response::submitted('scantron') ||
	 $ignore_sig) &&
	$tag eq 'numericalresponse') {
	delete($$args_ref{'sig'});
    }
    
    if ($tag eq 'formularesponse') {
	if ($$args_ref{'samples'}) {
	    $$args_ref{'type'}='fml';
	} else {
	    $$args_ref{'type'}='math';
	}
    } elsif ($tag eq 'numericalresponse') {
	$$args_ref{'type'}='float';
    } elsif ($tag eq 'stringresponse') {
        if ($$args_ref{'type'} eq '') {
            $$args_ref{'type'} = 'ci';
        }
    }

    &add_in_tag_answer($parstack,$safeeval);

    if (!%answer) {
	&Apache::lonxml::error("No answers are defined");
    }

    my (@final_awards,@final_msgs,@names);
    # $Apache::lonxml::debug = 1;	# DEBUG
    foreach my $name (keys(%answer)) {
	&Apache::lonxml::debug(" doing $name with ".join(':',@{ $answer{$name}{'answers'} }));
	
	${$safeeval->varglob('LONCAPA::CAPAresponse_answer')}=dclone($answer{$name});
	&setup_capa_response($args_ref,$response);
	use Time::HiRes;
	my $t0 = [Time::HiRes::gettimeofday()];
	my ($result,@msgs) = 
	    &Apache::run::run("&caparesponse_check_list()",$safeeval);
	&Apache::lonxml::debug("checking $name $result with $response took ".&Time::HiRes::tv_interval($t0));
	&Apache::lonxml::debug('msgs are '.join(':',@msgs));
	my ($awards)=split(/:/,$result);
	my @awards= split(/,/,$awards);
	my ($ad, $msg) = &Apache::inputtags::finalizeawards(\@awards,\@msgs);
	push(@final_awards,$ad);
	push(@final_msgs,$msg);
	push(@names,$name);
    }
    # $Apache::lonxml::debug = 0;	# DEBUG
    my ($ad, $msg, $name) = &Apache::inputtags::finalizeawards(\@final_awards,
							       \@final_msgs,
							       \@names,1);
    &Apache::lonxml::debug(" name of picked award is $name from ".join(', ',@names));
    return($ad,$msg, $name);
}

sub stringresponse_gradechange {
    my ($part,$id,$previous,$caller,$response,$ad,$type) = @_;
    return unless (ref($previous) eq 'HASH');
    my ($prevarray,$prevaward);
    my %typenames = (
                     cs => 'Case sensitive',
                     ci => 'Case insensitive',
                    );
    if ($caller eq 'cs') {
        return unless (ref($previous->{'version'}) eq 'ARRAY');
        $prevarray = $previous->{'version'};
        $prevaward = $previous->{'award'};
    } elsif ($caller eq 'ci') {
        return unless (ref($previous->{'versionci'}) eq 'ARRAY');
        $prevarray = $previous->{'versionci'};
        $prevaward = $previous->{'awardci'};
    } else {
        return;
    }
    my $count=0;
    my %count_lookup;
    foreach my $i (1..$Apache::lonhomework::history{'version'}) {
        my $prefix = $i.":resource.$part";
        next if (!exists($Apache::lonhomework::history{"$prefix.award"}));
        $count++;
        $count_lookup{$i} = $count;
    }
    my ($symb,$courseid,$domain,$name) = &Apache::lonnet::whichuser();
    my %coursedesc = &Apache::lonnet::coursedescription($courseid);
    my $cdom = $coursedesc{'domain'};
    my $versions = ' (submissions: '.join(', ',map {$count_lookup{$_} } @{$prevarray}).')';
    my $warning = "String Response ($typenames{$type}) grading discrepancy: award for response of $response changed from $prevaward".$versions." to $ad; user: $name:$domain in course: $courseid for part: $part response: $id for symb: $symb";
    &Apache::lonnet::logthis($warning);
    my $origmail = $Apache::lonnet::perlvar{'lonAdmEMail'};
    my $recipients = &Apache::loncommon::build_recipient_list(undef,'errormail',
                                                              $cdom,$origmail);
    if ($recipients ne '') {
        &Apache::lonmsg::sendemail($recipients,'Stringresponse Grading Discrepancy',$warning);
    }
    return;
}

sub add_in_tag_answer {
    my ($parstack,$safeeval,$response_level) = @_;
    my @answer=&Apache::lonxml::get_param_var('answer',$parstack,$safeeval,
					      $response_level);
    &Apache::lonxml::debug('answer is'.join(':',@answer));
    if (@answer && $answer[0] =~ /\S/) {
	$answer{$tag_internal_answer_name}= {'type' => 'ordered',
					     'answers' => [\@answer] };
    }
}

sub capa_formula_fix {
   my ($expression)=@_;
   return &Apache::response::implicit_multiplication($expression);
}

sub end_numericalresponse {
    my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;

    &Apache::lonxml::deregister('Apache::caparesponse',
			      ('answer','answergroup','value','array','unit',
			       'vector'));
    pop(@Apache::lonxml::namespace);

    my $increment=1;
    my $result = '';
    if (!$Apache::lonxml::default_homework_loaded) {
	&Apache::lonxml::default_homework_load($safeeval);
    }
    my $partid = $Apache::inputtags::part;
    my $id = $Apache::inputtags::response[-1];
    my $tag;
    my $safehole = new Safe::Hole;
    $safeeval->share_from('capa',['&caparesponse_capa_check_answer']);

    if (scalar(@$tagstack)) { $tag=$$tagstack[-1]; }
    if ( $target eq 'grade' && &Apache::response::submitted() ) {
	&Apache::response::setup_params($tag,$safeeval);
	if ($Apache::lonhomework::type eq 'exam' && 
	    (($tag eq 'formularesponse') || ($tag eq 'mathresponse'))) {
	    $increment=&Apache::response::scored_response($partid,$id);
	} else {
	    my $response = &Apache::response::getresponse();
	    if ( $response =~ /[^\s]/) {
		my %previous = &Apache::response::check_for_previous($response,$partid,$id);
		&Apache::lonxml::debug("submitted a $response<br>\n");
		&Apache::lonxml::debug($$parstack[-1] . "\n<br>");
		
		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,$name)=&check_submission($response,$partid,$id,
						      $tag,$parstack,
						      $safeeval);

		&Apache::lonxml::debug('ad is'.$ad);
		if ($ad eq 'SIG_FAIL') {
		    my ($sig_u,$sig_l)=
			&get_sigrange($Apache::inputtags::params{'sig'});
		    $msg=join(':',$msg,$sig_l,$sig_u);
		    &Apache::lonxml::debug("sigs bad $sig_u $sig_l ".
					   $Apache::inputtags::params{'sig'});
		}
		&Apache::lonxml::debug("\n<br>result:$result:$Apache::lonxml::curdepth<br>\n");
                if (($ad eq 'INCORRECT' || $ad eq 'APPROX_ANS' ||
                     $ad eq 'EXACT_ANS')) {
		    if ($Apache::lonhomework::type eq 'survey') {
		        $ad='SUBMITTED';
		    } elsif ($Apache::lonhomework::type eq 'surveycred') {
                        $ad='SUBMITTED_CREDIT';
                    } elsif ($Apache::lonhomework::type eq 'anonsurvey') {
                        $ad='ANONYMOUS';
                    } elsif ($Apache::lonhomework::type eq 'anonsurveycred') {
                        $ad='ANONYMOUS_CREDIT';                     
                    }
                }
		&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') {
	&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);
	    my $number_of_bubbles = scalar(@{ $bubble_values });
	    my $unit=&Apache::lonxml::get_param_var('unit',$parstack,
						    $safeeval);
	    if ($target eq 'web') {
		if ($tag eq 'numericalresponse') {
		    if ($unit=~/\S/) {$result.=' (in '.$unit.')<br /><br />';}
		    $result.= '<table border="1"><tr>';
		    my $previous=$Apache::lonhomework::history{"resource.$Apache::inputtags::part.$id.submission"};
		    for (my $ind=0;$ind<$number_of_bubbles;$ind++) {
			my $checked='';
			if ($previous eq $bubble_values->[$ind]) {
			    $checked=" checked='on' ";
			}
			$result.='<td><input type="radio" name="HWVAL_'.$id.
			    '" value="'.$bubble_values->[$ind].'" '.$checked
			    .' /><b>'.$alphabet[$ind].'</b>: '.
			    $bubble_display->[$ind].'</td>';
		    }
		    $result.='</tr></table>';
		}
	    } elsif ($target eq 'tex') {
		if ((defined $unit) and ($unit=~/\S/) and ($Apache::lonhomework::type eq 'exam')) {
		    $result.=' \textit{(in} \verb|'.$unit.'|\textit{)} ';
		}
		if ($tag eq 'numericalresponse') {
		    $result .= &make_horizontal_latex_bubbles($bubble_values, $bubble_display,
			'$\bigcirc$');
		} else {
		    $increment = &Apache::response::repetition();
		}
	    }
	}
        if (($target eq 'web') && ($tag eq 'formularesponse')
            && ($Apache::lonhomework::type ne 'exam') && ($Apache::inputtags::status['-1'] eq 'CAN_ANSWER')
	    && (&Apache::lonnet::EXT('resource.'.$partid.'_'.$id.'.turnoffeditor') ne 'yes')) {
            $result.=&Apache::response::edit_mathresponse_button($id,"HWVAL_$id");
        }
            
	&Apache::response::setup_prior_tries_hash(\&format_prior_response_numerical);
    } elsif ($target eq 'edit') {
	$result.='</td></tr>'.&Apache::edit::end_table;
    } elsif ($target eq 'answer' || $target eq 'analyze') {
	my $part_id="$partid.$id";
	if ($target eq 'analyze') {
	    push (@{ $Apache::lonhomework::analyze{"parts"} },$part_id);
	    $Apache::lonhomework::analyze{"$part_id.type"} = $tag;
	    my (@incorrect)=&Apache::lonxml::get_param_var('incorrect',$parstack,$safeeval);
	    if ($#incorrect eq 0) { @incorrect=(split(/,/,$incorrect[0])); }
	    push (@{ $Apache::lonhomework::analyze{"$part_id.incorrect"} }, @incorrect);
	    &Apache::response::check_if_computed($token,$parstack,
						 $safeeval,'answer');
	}
	if (scalar(@$tagstack)) {
	    &Apache::response::setup_params($tag,$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);
	
	if ($target eq 'answer') {
	    $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,
					     $id,$target,$parstack,$safeeval);
		$result.=&Apache::response::answer_part($tag,$correct);
	    }
	}
	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}=[];
		}
	    }
	    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 <i>$siglow - $sighigh</i>";
				$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);
			}
		    }
		}
		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];}
		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";
		}
	    }
	    #
	    # The computer's response must be cast in a form that 
	    # ensures it has sufficient significant figures.
	    # the only way to do this is to sprintf it into scientific notation
	    # due to the ambiguity of trailing zeros.
	    # See Bug 6577
	    #
	    my ($sigMax, $sigmin) = &get_sigrange($Apache::inputtags::params{'sig'});
	    my $sigfigs = $sigMax-1;

	    for (my $i = 0; $i < scalar @$response; $i++) {
		$response->[$i] = sprintf('%.' . $sigfigs . 'e', $response->[$i]);
	    }
	    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]).",'"'.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);
		    $error=&mt("Computer's answer is incorrect ([_1]).",'"'.join(', ',@$response).'"').' ';
		    if ($sigline ne '') {
			$error.=&mt('It is likely that the tolerance range [_1] or significant figures [_2] need to be adjusted.',$tolline,$sigline);
		    } else {
			$error.=&mt('It is likely that the tolerance range [_1] needs to be adjusted.',$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: <b>$unit</b>");
		    }
		} 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);
	    }
	    $result.=&Apache::response::next_answer($tag,$name);
	}
	if ($target eq 'answer') {
	    $result.=&Apache::response::answer_footer($tag);
	}
    }
    if ($target eq 'grade' || $target eq 'web' || $target eq 'answer' || 
	$target eq 'tex' || $target eq 'analyze') {
        if (($tag eq 'formularesponse') && ($target eq 'analyze')) {
            my $type = &Apache::lonnet::EXT('resource.'.$partid.'_'.$id.'.type');
            if ($type eq 'exam') {
                $increment = &Apache::response::repetition();
            }
        }
	&Apache::lonxml::increment_counter($increment,"$partid.$id");
	if ($target eq 'analyze') {
	    &Apache::lonhomework::set_bubble_lines();
	}
    }
    &Apache::response::end_response();
    return $result;
}

sub format_prior_response_numerical {
    my ($mode,$answer) = @_;
    if (ref($answer)) {
	my $result = '<table class="LC_prior_numerical"><tr>';
	foreach my $element (@{ $answer }) {
	    $result.= '<td><span class="LC_prior_numerical">'.
		&HTML::Entities::encode($element,'"<>&').'</span></td>';
	}
	$result.='</tr></table>';
	return $result;
    }
    return '<span class="LC_prior_numerical">'.
	    &HTML::Entities::encode($answer,'"<>&').'</span>';

}

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.",
		'<tt>'.join(', ',@{$counts{$count}}).'</tt>',
		$count);
	} (sort(keys(%counts))));
	&Apache::lonxml::error(&mt("All answers must have the same number of components. Varying numbers of answers were seen. ").$counts);
    }
    my $expected_number_of_inputs = (keys(%counts))[0];
    if ( $expected_number_of_inputs > 0 
	 && $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
    my $cell_width=0;
    foreach my $member (@$rbubble_values) {
	my $cell_width_real=0;
	if ($member=~/(\+|-)?(\d*)\.?(\d*)\s*\$?\\times\s*10\^{(\+|-)?(\d+)}\$?/) {
	    $cell_width_real=(length($2)+length($3)+length($5)+7)*$scale;
	} elsif ($member=~/(\d*)\.?(\d*)(E|e)(\+|-)?(\d*)/) {
	    $cell_width_real=(length($1)+length($2)+length($5)+9)*$scale;
        } elsif ($member=~/(\d*)\.(\d*)/) {
	    $cell_width_real=(length($1)+length($2)+3)*$scale;
	} else {
	    $cell_width_real=(length($member)+1)*$scale*0.9;
	}
	if ($cell_width_real>$cell_width) {$cell_width=$cell_width_real;}
    }
    $cell_width+=8; 
    my $textwidth;
    if ($env{'form.textwidth'} ne '') {
	$env{'form.textwidth'}=~/(\d*)\.?(\d*)/;
	$textwidth=$1.'.'.$2;
    } else {
	$env{'form.textwidth'}=~/(\d+)\.?(\d*)/;
	$textwidth=$1.'.'.$2;
    }
    my $bubbles_per_line=int($textwidth/$cell_width);
    if ($bubbles_per_line > $number_of_bubbles) {
	$bubbles_per_line=$number_of_bubbles;
    } elsif (($bubbles_per_line > $number_of_bubbles/2) 
	     && ($number_of_bubbles % 2==0)) {
	$bubbles_per_line=$number_of_bubbles/2;
    }
    if ($bubbles_per_line < 1) {
	$bubbles_per_line=1;
    }
    my $number_of_tables = int($number_of_bubbles/$bubbles_per_line);
    my @table_range = ();
    for (my $i=0;$i<$number_of_tables;$i++) {push @table_range,$bubbles_per_line;}
    if ($number_of_bubbles % $bubbles_per_line) {
	$number_of_tables++;
	push @table_range,($number_of_bubbles % $bubbles_per_line);
    }
    $cell_width-=8;
    $cell_width=$cell_width*3/4;
    return ($cell_width,$number_of_tables,@table_range);
}

sub format_number {
    my ($number,$format,$target,$safeeval)=@_;
    my $ans;
    if ($format eq '') {
	#What is the number? (integer,decimal,floating point)
	if ($number=~/^(\d*\.?\d*)(E|e)[+\-]?(\d*)$/) {
	    $format = '3e';
	} elsif ($number=~/^(\d*)\.(\d*)$/) {
	    $format = '4f';
	} elsif ($number=~/^(\d*)$/) {
	    $format = 'd';
	}
    }
    if (!$Apache::lonxml::default_homework_loaded) {
	&Apache::lonxml::default_homework_load($safeeval);
    }
    $ans=&Apache::run::run("&prettyprint(q\0$number\0,q\0$format\0,q\0$target\0)",$safeeval);
    return $ans;
}

sub make_numerical_bubbles {
    my ($part,$id,$target,$parstack,$safeeval) =@_;

    if (!%answer) {
	&Apache::lonxml::error(&mt("No answers defined for response [_1] in part [_2] to make bubbles for.",$id,$part));
	return ([],[],undef);
    }
    
    my $number_of_bubbles = 
	&Apache::response::get_response_param($part.'_'.$id,'numbubbles',8);

    #
    # Fixes for BZ 6519 - number of bubbles <= 0 or non-integer.
    # 
    $number_of_bubbles = int($number_of_bubbles + 0.5);
    if ($number_of_bubbles <= 0) {
	$number_of_bubbles = 8;
    }
    

    my ($format)=&Apache::lonxml::get_param_var('format',$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])); }
    
    my @bubble_values=();
    my @alphabet=('A'..'Z');

    &Apache::lonxml::debug("answer is $answer incorrect is @incorrect");
    my @oldseed=&Math::Random::random_get_seed();
    if (@incorrect) {
	&Apache::lonxml::debug("inside ".(scalar(@incorrect)+1 gt $number_of_bubbles));
	if (defined($incorrect[0]) &&
	    scalar(@incorrect)+1 >= $number_of_bubbles) {
	    &Apache::lonxml::debug("inside ".(scalar(@incorrect)+1).":$number_of_bubbles");
	    &Apache::response::setrandomnumber();
	    my @rand_inc=&Math::Random::random_permutation(@incorrect);
	    @bubble_values=@rand_inc[0..($number_of_bubbles-2)];
	    @bubble_values=sort {$a <=> $b} (@bubble_values,$answer);
	    &Apache::lonxml::debug("Answer was :$answer: returning :".$#bubble_values.": which are :".join(':',@bubble_values));
	    &Math::Random::random_set_seed(@oldseed);

	    my $correct;
	    for(my $i=0; $i<=$#bubble_values;$i++) {
		if ($bubble_values[$i] eq $answer) {
		    $correct = $alphabet[$i];
		    last;
		}
	    }

	    if (defined($format) && $format ne '') {
		my @bubble_display;
		foreach my $value (@bubble_values) {
		    push(@bubble_display,
			 &format_number($value,$format,$target,$safeeval));
		}
		return (\@bubble_values,\@bubble_display,$correct);
	    } else {
		return (\@bubble_values,\@bubble_values,$correct);
	    }
	}
	if (defined($incorrect[0]) &&
	    scalar(@incorrect)+1 < $number_of_bubbles) {
	    &Apache::lonxml::warning("Not enough incorrect answers were specified in the incorrect array, ignoring the specified incorrect answers and instead generating them (".join(',',@incorrect).").");
	}
    }
    my @factors = (1.13,1.17,1.25,1.33,1.45); #default values of factors
    my @powers = (1..$number_of_bubbles);
    &Apache::response::setrandomnumber();
    my $ind=&Math::Random::random_uniform_integer(1,0,$#powers);
    my $power = $powers[$ind];
    $ind=&Math::Random::random_uniform_integer(1,0,$#factors);
    my $factor = $factors[$ind];
    my @bubble_display;
    my $answerfactor=$answer;
    if ($answer==0) { 
       $answerfactor=&Math::Random::random_uniform_integer(1,1,100)/
                     &Math::Random::random_uniform_integer(1,1,10);
    }
    for ($ind=0;$ind<$number_of_bubbles;$ind++) {
	$bubble_values[$ind] = $answerfactor*($factor**($power-$powers[$#powers-$ind]));
	$bubble_display[$ind] = &format_number($bubble_values[$ind],
					       $format,$target,$safeeval);
    }
    my $correct = $alphabet[$number_of_bubbles-$power];
    if ($answer==0) {
       $correct='A';
       $bubble_values[0]=0;
       $bubble_display[0] = &format_number($bubble_values[0],
                                           $format,$target,$safeeval);
    }
    &Math::Random::random_set_seed(@oldseed);
    return (\@bubble_values,\@bubble_display,$correct);
}

##
# Produce LaTeX bubbles laid out horizontally given a set of bubble values:
#
# @param bubble_values  - reference to an array of bubble 'values'
# @param bubble_display - reference to the array of texts to display to the user
#                         for each bubble_value (this is mostly for numerical response
#                         when the displayed value may not be an exact
#                         representation of the bubble value. 
# @param bubble_fragment- The LaTeX fragment that will be plugged in to make
#                         the bubble itself. Note that the code will autonomously
#                         label each bubble with a lable...and that it's perfectly
#                         acceptable to use "" for the bubble_fragment.
# 
# @return string - the LaTeX fragment that produces the bubbles.
#
sub make_horizontal_latex_bubbles {
    my ($bubble_values, $bubble_display, $bubble_fragment)     = @_;
    my $result;

    my $number_of_bubbles = scalar(@{$bubble_values}); 

    # Get the number of rows and columns in each row of the bubble
    # table:

    my ($celllength, $number_of_tables, @table_range) =
	&get_table_sizes($number_of_bubbles, $bubble_display);

    my $j=0;
    my $cou=0;
    $result.='\vskip 2mm \noindent ';
    $result .= '\textbf{'.$Apache::lonxml::counter.'.} \vskip -3mm ';

    for (my $i=0;$i<$number_of_tables;$i++) {
	if ($i == 0) {
	    $result .= '\vskip -1mm ';
	} else {
	    $result .= '\vskip 1mm ';
	}
	$result.='\noindent \setlength{\tabcolsep}{2 mm}\hskip 2pc\begin{tabular}{';
	for (my $ind=0;$ind<$table_range[$j];$ind++) {
	    $result.='p{4 mm}p{'.$celllength.' mm}';
	}
	$result.='}';
	for (my $ind=$cou;$ind<$cou+$table_range[$j];$ind++) {
	    $result.='\hskip -4 mm {\small \textbf{ '.$alphabet[$ind].'}}'
		. $bubble_fragment 
		. '& \hskip -3 mm {\small '.$bubble_display->[$ind].'} ';
	    if ($ind != $cou+$table_range[$j]-1) {
		$result.=' & ';
	    }
	}
	$cou += $table_range[$j];
	$j++;
	$result.='\\\\\end{tabular}\vskip 0 mm ';
    }
    return $result;
}

sub get_tolrange {
    my ($ans,$tol)=@_;
    my ($high,$low);
    if ($tol =~ /%$/) {
	chop($tol);
	my $change=$ans*($tol/100.0);
	$high=$ans+$change;
	$low=$ans-$change;
    } else {
	$high=$ans+$tol;
	$low=$ans-$tol;
    }
    return ($high,$low);
}

sub get_sigrange {
    my ($sig)=@_;
    #&Apache::lonxml::debug("Got a sig of :$sig:");
    my $courseid=$env{'request.course.id'};
    if ($env{'request.state'} ne 'construct'
	&& lc($env{"course.$courseid.disablesigfigs"}) eq 'yes') {
	return (15,0);
    }
    my $sig_lbound;
    my $sig_ubound;
    if ($sig eq '') {
	$sig_lbound = 0; #SIG_LB_DEFAULT
	$sig_ubound =15; #SIG_UB_DEFAULT
    } else {
	($sig_lbound,$sig_ubound) = split(/,/,$sig);
	if (!defined($sig_lbound)) {
	    $sig_lbound = 0; #SIG_LB_DEFAULT
	    $sig_ubound =15; #SIG_UB_DEFAULT
	}
	if (!defined($sig_ubound)) { $sig_ubound=$sig_lbound; }
    }
    if (($sig_ubound<$sig_lbound) ||
	($sig_lbound > 15) ||
	($sig =~/(\+|-)/ ) ) {
	my $errormsg=&mt("Invalid Significant figures detected")." ($sig)";
	if ($env{'request.state'} eq 'construct') {
	    $errormsg.=
		&Apache::loncommon::help_open_topic('Significant_Figures');
	}
	&Apache::lonxml::error($errormsg);
    }
    return ($sig_ubound,$sig_lbound);
}

sub format_prior_response_string {
    my ($mode,$answer) =@_;
    return '<span class="LC_prior_string">'.
	    &HTML::Entities::encode($answer,'"<>&').'</span>';
}

sub start_stringresponse {
    my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
    my $result;
    my $id = &Apache::response::start_response($parstack,$safeeval);
    undef(%answer);
    if ($target eq 'meta') {
	$result=&Apache::response::meta_package_write('stringresponse');
    } elsif ($target eq 'edit') {
	$result.=&Apache::edit::tag_start($target,$token);
	$result.=&Apache::edit::text_arg('Answer:','answer',$token);
	$result.=&Apache::edit::select_arg('Type:','type',
			 [['cs','Case Sensitive'],['ci','Case Insensitive'],
			  ['mc','Case Insensitive, Any Order'],
			  ['re','Regular Expression']],$token);
	$result.=&Apache::edit::text_arg('String to display for answer:',
					 'answerdisplay',$token);
        $result.=&Apache::edit::text_arg('Pre-Processor Subroutine:','preprocess',
                                             $token,10);
	$result.=&Apache::edit::end_row().&Apache::edit::start_spanning_row();
    } elsif ($target eq 'modified') {
	my $constructtag;
	$constructtag=&Apache::edit::get_new_args($token,$parstack,
						  $safeeval,'answer',
						  'type','answerdisplay','preprocess');
	if ($constructtag) {
	    $result = &Apache::edit::rebuild_tag($token);
	    $result.=&Apache::edit::handle_insert();
	}
    } elsif ($target eq 'web') {
	if (  &Apache::response::show_answer() ) {
	    my $answer=
	       &Apache::lonxml::get_param('answerdisplay',$parstack,$safeeval);
	    if (!defined $answer || $answer eq '') {
		$answer=
		    &Apache::lonxml::get_param('answer',$parstack,$safeeval);
	    }
	    $Apache::inputtags::answertxt{$id}=[$answer];
	} 
    } elsif ($target eq 'answer' || $target eq 'grade') {
	&Apache::response::reset_params();
    }
    return $result;
}

sub end_stringresponse {
    my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;

    my $result = '';
    my $part=$Apache::inputtags::part;
    my $id=$Apache::inputtags::response[-1];
    my $answer=&Apache::lonxml::get_param('answer',$parstack,$safeeval);
    my $type=&Apache::lonxml::get_param('type',$parstack,$safeeval);
    my $answerdisplay=&Apache::lonxml::get_param('answerdisplay',$parstack,$safeeval);
    &Apache::lonxml::debug("current $answer ".$token->[2]);
    if (!$Apache::lonxml::default_homework_loaded) {
	&Apache::lonxml::default_homework_load($safeeval);
    }
    if ( $target eq 'grade' && &Apache::response::submitted() ) {
	&Apache::response::setup_params('stringresponse',$safeeval);
	$safeeval->share_from('capa',['&caparesponse_capa_check_answer']);
	if ($Apache::lonhomework::type eq 'exam' ||
	    &Apache::response::submitted('scantron')) {
	    &Apache::response::scored_response($part,$id);

	} else {
	    my $response = &Apache::response::getresponse();
	    if ( $response =~ /[^\s]/) {
		my %previous = &Apache::response::check_for_previous($response,
								    $part,$id,
                                                                    undef,$type);
		&Apache::lonxml::debug("submitted a $response<br>\n");
		&Apache::lonxml::debug($$parstack[-1] . "\n<br>");
		$Apache::lonhomework::results{"resource.$part.$id.submission"}=
		    $response;
		my ($ad,$msg);
		if ($type eq 're' ) { 
		    # if the RE wasn't in a var it likely got munged,
                    # thus grab it from the var directly
#		    my $testans=$token->[2]->{'answer'};
#		    if ($testans !~ m/^\s*\$/) {
#			$answer=$token->[2]->{'answer'};
#		    }
		    ${$safeeval->varglob('LONCAPA::response')}=$response;
                    my $preprocess=&Apache::lonxml::get_param('preprocess',$parstack,$safeeval);
                    $preprocess=~s/^\&//;
                    if (defined($preprocess)) {
                        &Apache::run::run('$LONCAPA::response=&'.$preprocess.'($LONCAPA::response);',$safeeval);
                    }
		    $result = &Apache::run::run('if ($LONCAPA::response=~m'.$answer.') { return 1; } else { return 0; }',$safeeval);
		    &Apache::lonxml::debug("current $response");
		    &Apache::lonxml::debug("current $answer");
		    $ad = ($result) ? 'APPROX_ANS' : 'INCORRECT';
		} else {
		    my @args = ('type','preprocess');
		    my $args_ref = &setup_capa_args($safeeval,$parstack,
						    \@args,$response);
                    if ($$args_ref{'type'} eq '') {
                        $$args_ref{'type'} = 'ci';
                    }
		    &add_in_tag_answer($parstack,$safeeval);
		    my (@final_awards,@final_msgs,@names,%ansstring);
		    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);
                        if ($$args_ref{'type'} =~ /^c[si]$/) {
                            $ansstring{$name} = pop(@msgs);
                            my $control_chars_removed = pop(@msgs);
                            my $error = pop(@msgs);
                            if (($error ne '') || 
                                ($control_chars_removed)) {
                                my ($symb,$courseid,$sdomain,$sname) =
                                    &Apache::lonnet::whichuser();
                                if ($control_chars_removed) {
                                    my $showresponse = $response;
                                    if ($response =~ /[\000-\037]/) {
                                        $response =~ s/[\000-\037]//g;
                                    }
                                    if ($showresponse  =~ /[\r\n\f]/) {
                                        my @lines = split(/[\r\n\f]+/,$showresponse);
                                        $showresponse = join('\\n',@lines);
                                    }
                                    &Apache::lonnet::logthis("Stringresponse grading: control characters stripped from submission ".$showresponse." for $sname:$sdomain in $courseid for part: $part response: $id and symb: $symb");
                                    $Apache::lonhomework::results{"resource.$part.$id.submission"} = $response;
                                }
                                if ($error ne '') {
                                    &Apache::lonnet::logthis("Stringresponse grading error: $error for $sname:$sdomain in $courseid for part: $part response: $id and symb: $symb");
                                }
                            }
                        }
			&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<br>result:$result:$Apache::lonxml::curdepth<br>\n");
		    }
		    ($ad, $msg, my $name) = 
			&Apache::inputtags::finalizeawards(\@final_awards,
							   \@final_msgs,
							   \@names,1);
                    if (keys(%ansstring) > 0) {
                        $Apache::lonhomework::results{"resource.$part.$id.answerstring"} = &Apache::lonnet::hash2str(%ansstring);
                    }
		}
                if (($ad eq 'INCORRECT' || $ad eq 'APPROX_ANS' ||
                     $ad eq 'EXACT_ANS')) {
		    if ($Apache::lonhomework::type eq 'survey') {
		        $ad='SUBMITTED';
		    } elsif ($Apache::lonhomework::type eq 'surveycred') {
                        $ad='SUBMITTED_CREDIT';
                    } elsif ($Apache::lonhomework::type eq 'anonsurvey') {
                        $ad='ANONYMOUS';
                    } elsif ($Apache::lonhomework::type eq 'anonsurveycred') {
                        $ad='ANONYMOUS_CREDIT';
                    }
                }
                unless (($env{'request.state'} eq 'construct') || 
                        ($Apache::lonhomework::type eq 'randomizetry')) {
                    if (($ad eq 'INCORRECT' || $ad eq 'APPROX_ANS' || $ad eq 'EXACT_ANS')) {
                        if ($previous{'used'}) {
                            if ($ad ne $previous{'award'}) {
                                if (($previous{'award'} eq 'INCORRECT' || 
                                     $previous{'award'} eq 'APPROX_ANS' ||
                                     $previous{'award'} eq 'EXACT_ANS')) {
                                    &stringresponse_gradechange($part,$id,\%previous,
                                                                'cs',$response,$ad,$type);
                                }
                            }
                        } elsif ($previous{'usedci'}) {
                            if ($ad ne $previous{'awardci'}) {
                                if (($previous{'awardci'} eq 'INCORRECT' || 
                                     $previous{'awardci'} eq 'APPROX_ANS' ||
                                     $previous{'awardci'} eq 'EXACT_ANS')) {
                                    &stringresponse_gradechange($part,$id,\%previous,
                                                                'ci',$response,$ad,$type);
                                }
                            }
                        }
                    }
                }
		&Apache::response::handle_previous(\%previous,$ad);
		$Apache::lonhomework::results{"resource.$part.$id.awarddetail"}=$ad;
		$Apache::lonhomework::results{"resource.$part.$id.awardmsg"}=$msg;
	    }
	}
    } elsif ($target eq 'answer' || $target eq 'analyze') {
	&add_in_tag_answer($parstack,$safeeval);
	if ($target eq 'analyze') {
	    push (@{ $Apache::lonhomework::analyze{"parts"} },"$part.$id");
	    $Apache::lonhomework::analyze{"$part.$id.type"} = 'stringresponse';
	    &Apache::response::check_if_computed($token,$parstack,$safeeval,
						 'answer');
	}
	&Apache::response::setup_params('stringresponse',$safeeval);
	if ($target eq 'answer') {
	    $result.=&Apache::response::answer_header('stringresponse');
	}
	foreach my $name (keys(%answer)) {
	    my @answers = @{ $answer{$name}{'answers'} };
	    for (my $i=0;$i<=$#answers;$i++) {
		my $answer_part = $answers[$i];
		foreach my $element (@{$answer_part}) {
		    if ($target eq 'answer') {
			$result.=&Apache::response::answer_part('stringresponse',
								$element);
                        if ($env{'form.grade_retrieveanswers'}) {
                            $env{'form.grade_answers.resource.'.$part.'.'.$id} = $element;
                        }
		    } elsif ($target eq 'analyze') {
			push (@{ $Apache::lonhomework::analyze{"$part.$id.answer"}{$name}[$i] },
			      $element);
		    }
		}
		if ($target eq 'answer' && $type eq 're') {
		    $result.=&Apache::response::answer_part('stringresponse',
							    $answerdisplay);
		}
	    }
	}
	my $string='Case Insensitive';
	if ($type eq 'mc') {
	    $string='Multiple Choice';
	} elsif ($type eq 'cs') {
	    $string='Case Sensitive';
	} elsif ($type eq 'ci') {
	    $string='Case Insensitive';
	} elsif ($type eq 're') {
	    $string='Regular Expression';
	}
	if ($target eq 'answer') {
	    if ($env{'form.answer_output_mode'} eq 'tex') {
		$result.=&Apache::response::answer_part('stringresponse',
							"$string");
	    } else {
		$result.=&Apache::response::answer_part('stringresponse',
							"<b>$string</b>");
	    }
	} elsif ($target eq 'analyze') {
	    push (@{$Apache::lonhomework::analyze{"$part.$id.str_type"}},
		  $type);
	}
	if ($target eq 'answer') {
	    $result.=&Apache::response::answer_footer('stringresponse');
	}
    } elsif ($target eq 'edit') {
	$result.='</td></tr>'.&Apache::edit::end_table;
    } elsif ($target eq 'web' || $target eq 'tex') {
	&Apache::response::setup_prior_tries_hash(\&format_prior_response_string);
    }
    if ($target eq 'grade' || $target eq 'web' || $target eq 'answer' || 
	$target eq 'tex' || $target eq 'analyze') {
        my $repetition = &Apache::response::repetition();
	&Apache::lonxml::increment_counter($repetition,"$part.$id");
	if ($target eq 'analyze') {
	    &Apache::lonhomework::set_bubble_lines();
	}
    }
    &Apache::response::end_response;
    return $result;
}

sub start_formularesponse {
    my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
    my $result;
    if ($target eq 'meta') {
	&Apache::response::start_response($parstack,$safeeval);
	$result=&Apache::response::meta_package_write('formularesponse');
	&Apache::response::end_response();
    } else {
	$result.=&start_numericalresponse(@_);
    }
    return $result;
}

sub end_formularesponse {
    return end_numericalresponse(@_);
}

1;
__END__


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>