| … | |
… | |
| 161 | # |
161 | # |
| 162 | # Quote HTML characters |
162 | # Quote HTML characters |
| 163 | # |
163 | # |
| 164 | sub protectHTML { |
164 | sub protectHTML { |
| 165 | my $string = shift; |
165 | my $string = shift; |
|
|
166 | return $string if eval ('$main::displayMode') eq 'TeX'; |
| 166 | $string =~ s/&/\&/g; |
167 | $string =~ s/&/\&/g; |
| 167 | $string =~ s/</\</g; |
168 | $string =~ s/</\</g; |
| 168 | $string =~ s/>/\>/g; |
169 | $string =~ s/>/\>/g; |
| 169 | $string; |
170 | $string; |
| 170 | } |
171 | } |
| … | |
… | |
| 441 | my $self = shift; |
442 | my $self = shift; |
| 442 | return ( |
443 | return ( |
| 443 | Value::Real->cmp_defaults, |
444 | Value::Real->cmp_defaults, |
| 444 | showHints => undef, |
445 | showHints => undef, |
| 445 | showLengthHints => undef, |
446 | showLengthHints => undef, |
|
|
447 | showParenHints => undef, |
| 446 | # partialCredit => undef, |
448 | # partialCredit => undef, |
| 447 | partialCredit => 0, # only allow this once WW can deal with partial credit |
449 | partialCredit => 0, # only allow this once WW can deal with partial credit |
| 448 | ordered => 0, |
450 | ordered => 0, |
| 449 | entry_type => undef, |
451 | entry_type => undef, |
| 450 | list_type => undef, |
452 | list_type => undef, |
| 451 | typeMatch => Value::makeValue($self->{data}[0]), |
453 | typeMatch => Value::makeValue($self->{data}[0]), |
| 452 | allowParens => 0, |
454 | requireParenMatch => 1, |
| 453 | showParens => 0, |
455 | removeParens => 1, |
| 454 | ); |
456 | ); |
| 455 | } |
457 | } |
| 456 | |
458 | |
| 457 | # |
459 | # |
| 458 | # Match anything but formulas |
460 | # Match anything but formulas |
| … | |
… | |
| 463 | # Handle removal of outermost parens in correct answer. |
465 | # Handle removal of outermost parens in correct answer. |
| 464 | # |
466 | # |
| 465 | sub cmp { |
467 | sub cmp { |
| 466 | my $self = shift; |
468 | my $self = shift; |
| 467 | my $cmp = $self->SUPER::cmp(@_); |
469 | my $cmp = $self->SUPER::cmp(@_); |
| 468 | if (!$cmp->{rh_ans}{showParens}) { |
470 | if ($cmp->{rh_ans}{removeParens}) { |
| 469 | $self->{open} = $self->{close} = ''; |
471 | $self->{open} = $self->{close} = ''; |
| 470 | $cmp->ans_hash(correct_ans => $self->stringify); |
472 | $cmp->ans_hash(correct_ans => $self->stringify); |
| 471 | } |
473 | } |
| 472 | return $cmp; |
474 | return $cmp; |
| 473 | } |
475 | } |
| … | |
… | |
| 480 | # get the paramaters |
482 | # get the paramaters |
| 481 | # |
483 | # |
| 482 | my $showTypeWarnings = $ans->{showTypeWarnings}; |
484 | my $showTypeWarnings = $ans->{showTypeWarnings}; |
| 483 | my $showHints = getOption($ans,'showHints'); |
485 | my $showHints = getOption($ans,'showHints'); |
| 484 | my $showLengthHints = getOption($ans,'showLengthHints'); |
486 | my $showLengthHints = getOption($ans,'showLengthHints'); |
|
|
487 | my $showParenHints = getOption($ans,'showLengthHints'); |
| 485 | my $partialCredit = getOption($ans,'partialCredit'); |
488 | my $partialCredit = getOption($ans,'partialCredit'); |
| 486 | my $ordered = $ans->{ordered}; my $allowParens = $ans->{allowParens}; |
489 | my $ordered = $ans->{ordered}; |
|
|
490 | my $requireParenMatch = $ans->{requireParenMatch}; |
| 487 | my $typeMatch = $ans->{typeMatch}; |
491 | my $typeMatch = $ans->{typeMatch}; |
| 488 | my $value = $ans->{entry_type}; |
492 | my $value = $ans->{entry_type}; |
| 489 | my $ltype = $ans->{list_type} || lc($self->type); |
493 | my $ltype = $ans->{list_type} || lc($self->type); |
| 490 | |
494 | |
| 491 | $value = (Value::isValue($typeMatch)? lc($typeMatch->cmp_class): 'value') |
495 | $value = (Value::isValue($typeMatch)? lc($typeMatch->cmp_class): 'value') |
| … | |
… | |
| 497 | |
501 | |
| 498 | # |
502 | # |
| 499 | # Get the lists of correct and student answers |
503 | # Get the lists of correct and student answers |
| 500 | # (split formulas that return lists or unions) |
504 | # (split formulas that return lists or unions) |
| 501 | # |
505 | # |
| 502 | my @correct = (); |
506 | my @correct = (); my ($cOpen,$cClose); |
| 503 | if ($self->class ne 'Formula') {@correct = $self->value} |
507 | if ($self->class ne 'Formula') { |
|
|
508 | @correct = $self->value; |
|
|
509 | $cOpen = $ans->{correct_value}{open}; $cClose = $ans->{correct_value}{close}; |
|
|
510 | } else { |
| 504 | else {@correct = Value::List->splitFormula($self,$ans)} |
511 | @correct = Value::List->splitFormula($self,$ans); |
| 505 | my $student = $ans->{student_value}; |
512 | $cOpen = $self->{tree}{open}; $cClose = $self->{tree}{close}; |
| 506 | my @student = ($student); |
513 | } |
|
|
514 | my $student = $ans->{student_value}; my @student = ($student); |
|
|
515 | my ($sOpen,$sClose) = ('',''); |
| 507 | if (Value::isFormula($student) && $student->type eq $self->type) { |
516 | if (Value::isFormula($student) && $student->type eq $self->type) { |
| 508 | @student = Value::List->splitFormula($student,$ans); |
517 | @student = Value::List->splitFormula($student,$ans); |
|
|
518 | $sOpen = $student->{tree}{open}; $sClose = $student->{tree}{close}; |
| 509 | } elsif ($student->class ne 'Formula' && $student->class eq $self->type && |
519 | } elsif ($student->class ne 'Formula' && $student->class eq $self->type) { |
| 510 | ($allowParens || (!$student->{open} && !$student->{close}))) { |
|
|
| 511 | @student = @{$student->{data}}; |
520 | @student = @{$student->{data}}; |
|
|
521 | $sOpen = $student->{open}; $sClose = $student->{close}; |
| 512 | } |
522 | } |
| 513 | return if $ans->{split_error}; |
523 | return if $ans->{split_error}; |
|
|
524 | # |
|
|
525 | # Check for parenthesis match |
|
|
526 | # |
|
|
527 | if ($requireParenMatch && ($sOpen ne $cOpen || $sClose ne $cClose)) { |
|
|
528 | if ($showParenHints && !($ans->{ignoreStrings} && $student->type eq 'String')) { |
|
|
529 | my $message = "The parentheses for your $ltype "; |
|
|
530 | if (($cOpen || $cClose) && ($sOpen || $sClose)) |
|
|
531 | {$message .= "are of the wrong type"} |
|
|
532 | elsif ($sOpen || $sClose) {$message .= "should be removed"} |
|
|
533 | else {$message .= "are missing"} |
|
|
534 | $self->cmp_Error($ans,$message); |
|
|
535 | } |
|
|
536 | return; |
|
|
537 | } |
|
|
538 | # |
|
|
539 | # Check for empty lists |
|
|
540 | # |
| 514 | if (scalar(@correct) == 0 && scalar(@student) == 0) {$ans->score(1); return} |
541 | if (scalar(@correct) == 0 && scalar(@student) == 0) {$ans->score(1); return} |
| 515 | |
542 | |
| 516 | # |
543 | # |
| 517 | # Initialize the score |
544 | # Initialize the score |
| 518 | # |
545 | # |
| … | |
… | |
| 623 | |
650 | |
| 624 | return Value::Real::cmp_defaults($self,@_) unless $self->type eq 'List'; |
651 | return Value::Real::cmp_defaults($self,@_) unless $self->type eq 'List'; |
| 625 | |
652 | |
| 626 | return ( |
653 | return ( |
| 627 | Value::List::cmp_defaults($self,@_), |
654 | Value::List::cmp_defaults($self,@_), |
|
|
655 | removeParens => $self->{autoFormula}, |
| 628 | typeMatch => Value::Formula->new(($self->createRandomPoints(1))[1]->[0]{data}[0]), |
656 | typeMatch => Value::Formula->new(($self->createRandomPoints(1))[1]->[0]{data}[0]), |
| 629 | ); |
657 | ); |
| 630 | } |
658 | } |
| 631 | |
659 | |
| 632 | # |
660 | # |
| … | |
… | |
| 646 | # Handle removal of outermost parens in a list. |
674 | # Handle removal of outermost parens in a list. |
| 647 | # |
675 | # |
| 648 | sub cmp { |
676 | sub cmp { |
| 649 | my $self = shift; |
677 | my $self = shift; |
| 650 | my $cmp = $self->SUPER::cmp(@_); |
678 | my $cmp = $self->SUPER::cmp(@_); |
| 651 | if (!$cmp->{rh_ans}{showParens} && $self->type eq 'List') { |
679 | if ($cmp->{rh_ans}{removeParens} && $self->type eq 'List') { |
| 652 | $self->{tree}{open} = $self->{tree}{close} = ''; |
680 | $self->{tree}{open} = $self->{tree}{close} = ''; |
| 653 | $cmp->ans_hash(correct_ans => $self->stringify); |
681 | $cmp->ans_hash(correct_ans => $self->stringify); |
| 654 | } |
682 | } |
| 655 | return $cmp; |
683 | return $cmp; |
| 656 | } |
684 | } |