[system] / trunk / webwork / system / courseScripts / PGbasicmacros.pl Repository:
ViewVC logotype

View of /trunk/webwork/system/courseScripts/PGbasicmacros.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 463 - (download) (as text) (annotate)
Wed Aug 14 23:06:00 2002 UTC (10 years, 9 months ago) by jj
File size: 51999 byte(s)
Added new randomization function, list_random, which takes a list of values
and picks one from the list.

    1 #!/usr/local/bin/webwork-perl
    2 
    3 ####################################################################
    4 # Copyright @ 1995-1998 University of Rochester
    5 # All Rights Reserved
    6 ####################################################################
    7 
    8 =head1 NAME
    9 
   10     PGbasicmacros.pl --- located in the courseScripts directory
   11 
   12 =head1 SYNPOSIS
   13 
   14 
   15 
   16 =head1 DESCRIPTION
   17 
   18 
   19 
   20 =cut
   21 
   22 # this is equivalent to use strict, but can be used within the Safe compartment.
   23 BEGIN{
   24   be_strict;
   25 }
   26 
   27 
   28 my $displayMode=$main::displayMode;
   29 
   30 my ($PAR,
   31   $BR,
   32   $LQ,
   33   $RQ,
   34   $BM,
   35   $EM,
   36   $BDM,
   37   $EDM,
   38   $LTS,
   39   $GTS,
   40   $LTE,
   41   $GTE,
   42   $BEGIN_ONE_COLUMN,
   43   $END_ONE_COLUMN,
   44   $SOL,
   45   $SOLUTION,
   46   $HINT,
   47   $US,
   48   $SPACE,
   49   $BBOLD,
   50   $EBOLD,
   51   $BITALIC,
   52   $EITALIC,
   53   $BCENTER,
   54   $ECENTER,
   55   $HR,
   56   $LBRACE,
   57   $RBRACE,
   58   $LB,
   59   $RB,
   60   $DOLLAR,
   61   $PERCENT,
   62   $CARET,
   63   $PI,
   64   $E,
   65   @ALPHABET,
   66   );
   67 
   68 sub _PGbasicmacros_init {
   69     $displayMode    =$main::displayMode;
   70   $main::PAR        = PAR();
   71   $main::BR       = BR();
   72   $main::LQ       = LQ();
   73   $main::RQ       = RQ();
   74   $main::BM       = BM();
   75   $main::EM       = EM();
   76   $main::BDM        = BDM();
   77   $main::EDM        = EDM();
   78   $main::LTS        = LTS();
   79   $main::GTS        = GTS();
   80   $main::LTE        = LTE();
   81   $main::GTE        = GTE();
   82   $main::BEGIN_ONE_COLUMN = BEGIN_ONE_COLUMN();
   83   $main::END_ONE_COLUMN = END_ONE_COLUMN();
   84   $main::SOL        = SOLUTION_HEADING();
   85   $main::SOLUTION     = SOLUTION_HEADING();
   86   $main::HINT       = HINT_HEADING();
   87   $main::US       = US();
   88   $main::SPACE      = SPACE();
   89   $main::BBOLD      = BBOLD();
   90   $main::EBOLD      = EBOLD();
   91   $main::BITALIC      = BITALIC();
   92   $main::EITALIC          = EITALIC();
   93   $main::BCENTER          = BCENTER();
   94   $main::ECENTER          = ECENTER();
   95   $main::HR       = HR();
   96   $main::LBRACE     = LBRACE();
   97   $main::RBRACE     = RBRACE();
   98   $main::LB       = LB();
   99   $main::RB       = RB();
  100   $main::DOLLAR     = DOLLAR();
  101   $main::PERCENT      = PERCENT();
  102   $main::CARET      = CARET();
  103   $main::PI       = PI();
  104   $main::E        = E();
  105   @main::ALPHABET     = ('A'..'ZZ');
  106 
  107   $PAR        = PAR();
  108   $BR       = BR();
  109   $LQ       = LQ();
  110   $RQ       = RQ();
  111   $BM       = BM();
  112   $EM       = EM();
  113   $BDM        = BDM();
  114   $EDM        = EDM();
  115   $LTS        = LTS();
  116   $GTS        = GTS();
  117   $LTE        = LTE();
  118   $GTE        = GTE();
  119   $BEGIN_ONE_COLUMN = BEGIN_ONE_COLUMN();
  120   $END_ONE_COLUMN = END_ONE_COLUMN();
  121   $SOL        = SOLUTION_HEADING();
  122   $SOLUTION     = SOLUTION_HEADING();
  123   $HINT       = HINT_HEADING();
  124   $US       = US();
  125   $SPACE      = SPACE();
  126   $BBOLD      = BBOLD();
  127   $EBOLD      = EBOLD();
  128   $HR       = HR();
  129   $LBRACE     = LBRACE();
  130   $RBRACE     = RBRACE();
  131   $LB       = LB();
  132   $RB       = RB();
  133   $DOLLAR     = DOLLAR();
  134   $PERCENT      = PERCENT();
  135   $CARET      = CARET();
  136   $PI       = PI();
  137   $E        = E();
  138   @ALPHABET     = ('A'..'ZZ');
  139 
  140 
  141 
  142 }
  143 
  144 =head2  Answer blank macros:
  145 
  146 These produce answer blanks of various sizes or pop up lists or radio answer buttons.
  147 The names for the answer blanks are
  148 generated implicitly.
  149 
  150   ans_rule( width )
  151   tex_ans_rule( width )
  152   ans_radio_buttons(value1=>label1, value2,label2 => value3,label3=>...)
  153   pop_up_list(@list)  # list consists of (value => label,  PR => "Product rule",...)
  154 
  155 To indicate the checked position of radio buttons put a '%' in front of the value: C<ans_radio_buttons(1, 'Yes','%2','No')>
  156 will have 'No' checked.  C<tex_ans_rule> works inside math equations in C<HTML_tth> mode.  It does not work in C<Latex2HTML> mode
  157 since this mode produces gif pictures.
  158 
  159 
  160 The following method is defined in F<PG.pl> for entering the answer evaluators corresponding
  161 to answer rules with automatically generated names.  The answer evaluators are matched with the
  162 answer rules in the order in which they appear on the page.
  163 
  164   ANS(ans_evaluator1, ans_evaluator2,...);
  165 
  166 These are more primitive macros which produce answer blanks for specialized cases when complete
  167 control over the matching of answers blanks and answer evaluators is desired.
  168 The names of the answer blanks must be generated manually, and it is best if they do NOT begin
  169 with the default answer prefix (currently AnSwEr).
  170 
  171   labeled_ans_rule(name, width)  # an alias for NAMED_ANS_RULE where width defaults to 20 if omitted.
  172 
  173   NAMED_ANS_RULE(name, width)
  174   NAMED_ANS_BOX(name, rows, cols)
  175   NAMED_ANS_RADIO(name, value,label,)
  176   NAMED_ANS_RADIO_EXTENSION(name, value,label)
  177   NAMED_ANS_RADIO_BUTTONS(name,value1,label1,value2,label2,...)
  178   check_box('-name' =>answer5,'-value' =>'statement3','-label' =>'I loved this course!'   )
  179   NAMED_POP_UP_LIST($name, @list) # list consists of (value => tag,  PR => "Product rule",...)
  180 
  181 (Name is the name of the variable, value is the value given to the variable when this option is selected,
  182 and label is the text printed next to the button or check box.    Check box variables can have multiple values.)
  183 
  184 NAMED_ANS_RADIO_BUTTONS creates a sequence of NAMED_ANS_RADIO and NAMED_ANS_RADIO_EXTENSION  items which
  185 are  output either as an array or, in scalar context, as the array glued together with spaces.  It is
  186 usually easier to use this than to manually construct the radio buttons by hand.  However, sometimes
  187  extra flexibility is desiredin which case:
  188 
  189 When entering radio buttons using the "NAMED" format, you should use NAMED_ANS_RADIO button for the first button
  190 and then use NAMED_ANS_RADIO_EXTENSION for the remaining buttons.  NAMED_ANS_RADIO requires a matching answer evalutor,
  191 while NAMED_ANS_RADIO_EXTENSION does not. The name used for NAMED_ANS_RADIO_EXTENSION should match the name
  192 used for NAMED_ANS_RADIO (and the associated answer evaluator).
  193 
  194 
  195 The following method is defined in  F<PG.pl> for entering the answer evaluators corresponding
  196 to answer rules with automatically generated names.  The answer evaluators are matched with the
  197 answer rules in the order in which they appear on the page.
  198 
  199       NAMED_ANS(name1 => ans_evaluator1, name2 => ans_evaluator2,...);
  200 
  201 These auxiliary macros are defined in PG.pl
  202 
  203 
  204   NEW_ANS_NAME( number );   # produces a new answer blank name from a number by adding a prefix (AnSwEr)
  205                             # and registers this name as an implicitly labeled answer
  206                             # Its use is paired with each answer evaluator being entered using ANS()
  207 
  208     ANS_NUM_TO_NAME(number);  # adds the prefix (AnSwEr) to the number, but does nothing else.
  209 
  210   RECORD_ANS_NAME( name );  # records the order in which the answer blank  is rendered
  211                             # This is called by all of the constructs above, but must
  212                             # be called explicitly if an input blank is constructed explictly
  213                             # using HTML code.
  214 
  215 These are legacy macros:
  216 
  217   ANS_RULE( number, width );                  # equivalent to NAMED_ANS_RULE( NEW_ANS_NAME(number), width)
  218   ANS_BOX( question_number,height, width );       # equivalent to NAMED_ANS_BOX( NEW_ANS_NAME(number), height, width)
  219   ANS_RADIO( question_number, value,tag );        # equivalent to NAMED_ANS_RADIO( NEW_ANS_NAME(number), value,tag)
  220   ANS_RADIO_OPTION( question_number, value,tag );   # equivalent to NAMED_ANS_RADIO_EXTENSION( ANS_NUM_TO_NAME(number), value,tag)
  221 
  222 
  223 =cut
  224 
  225 sub labeled_ans_rule {   # syntactic sugar for NAMED_ANS_RULE
  226   my($name,$col) = @_;
  227   $col = 20 unless defined($col);
  228   NAMED_ANS_RULE($name,$col);
  229 }
  230 
  231 sub NAMED_ANS_RULE {
  232   my($name,$col) = @_;
  233   my $len = 0.07*$col;
  234   my $answer_value = '';
  235   $answer_value = ${$main::inputs_ref}{$name} if    defined(${$main::inputs_ref}{$name});
  236     if ($answer_value =~ /\0/ ) {
  237       my @answers = split("\0", $answer_value);
  238       $answer_value = shift(@answers);  # use up the first answer
  239       $main::rh_sticky_answers{$name}=\@answers;  # store the rest
  240       $answer_value= '' unless defined($answer_value);
  241   } elsif (ref($answer_value) eq 'ARRAY') {
  242     my @answers = @{ $answer_value};
  243       $answer_value = shift(@answers);  # use up the first answer
  244       $main::rh_sticky_answers{$name}=\@answers;  # store the rest
  245       $answer_value= '' unless defined($answer_value);
  246   }
  247 
  248   $answer_value =~ tr/$@//d;   ## make sure student answers can not be interpolated by e.g. EV3
  249   $name = RECORD_ANS_NAME($name);
  250   MODES(
  251     TeX => "\\mbox{\\parbox[t]{10pt}{\\hrulefill}}\\hrulefill\\quad ",
  252     Latex2HTML => qq!\\begin{rawhtml}\n<INPUT TYPE=TEXT SIZE=$col NAME=\"$name\" VALUE = \"\">\n\\end{rawhtml}\n!,
  253     HTML => "<INPUT TYPE=TEXT SIZE=$col NAME=\"$name\" VALUE = \"$answer_value\">\n"
  254   );
  255 }
  256 
  257 sub NAMED_ANS_RULE_OPTION {   # deprecated
  258   &NAMED_ANS_RULE_EXTENSION;
  259 }
  260 
  261 sub NAMED_ANS_RULE_EXTENSION {
  262   my($name,$col) = @_;
  263   my $len = 0.07*$col;
  264   my $answer_value = '';
  265   $answer_value = ${$main::inputs_ref}{$name} if defined(${$main::inputs_ref}{$name});
  266   if ( defined($main::rh_sticky_answers{$name}) ) {
  267     $answer_value = shift( @{$main::rh_sticky_answers{$name}});
  268     $answer_value = '' unless defined($answer_value);
  269   }
  270   $answer_value =~ tr/$@//d;   ## make sure student answers can not be interpolated by e.g. EV3
  271   MODES(
  272     TeX => '\\hrulefill\\quad ',
  273     Latex2HTML => qq!\\begin{rawhtml}\n<INPUT TYPE=TEXT SIZE=$col NAME=\"$name\" VALUE = \"\">\n\\end{rawhtml}\n!,
  274     HTML => qq!<INPUT TYPE=TEXT SIZE=$col NAME = "$name" VALUE = "$answer_value">\n!
  275   );
  276 }
  277 
  278 sub ANS_RULE {  #deprecated
  279   my($number,$col) = @_;
  280   my $name = NEW_ANS_NAME($number);
  281     NAMED_ANS_RULE($name,$col);
  282 }
  283 
  284 
  285 sub  NAMED_ANS_BOX {
  286   my($name,$row,$col) = @_;
  287   $row = 10 unless defined($row);
  288   $col = 80 unless defined($col);
  289   $name = RECORD_ANS_NAME($name);
  290   my $len = 0.07*$col;
  291   my $height = .07*$row;
  292   my $answer_value = '';
  293   $answer_value = $main::inputs_ref->{$name} if defined( $main::inputs_ref->{$name} );
  294   $answer_value =~ tr/$@//d;   ## make sure student answers can not be interpolated by e.g. EV3
  295   my $out = M3(
  296        qq!\\vskip $height in \\hrulefill\\quad !,
  297        qq!\\begin{rawhtml}<TEXTAREA NAME="$name" ROWS="$row" COLS="$col"
  298                WRAP="VIRTUAL">$answer_value</TEXTAREA>\\end{rawhtml}!,
  299          qq!<TEXTAREA NAME="$name" ROWS="$row" COLS="$col"
  300                WRAP="VIRTUAL">$answer_value</TEXTAREA>!
  301          );
  302   $out;
  303 }
  304 
  305 sub  ANS_BOX { #deprecated
  306   my($number,$row,$col) = @_;
  307   my $name = NEW_ANS_NAME($number);
  308     NAMED_ANS_BOX($name,$row,$col);
  309 }
  310 
  311 sub NAMED_ANS_RADIO {
  312   my $name = shift;
  313   my $value = shift;
  314     my $tag =shift;
  315     $name = RECORD_ANS_NAME($name);
  316     my $checked = '';
  317     if ($value =~/^\%/) {
  318       $value =~ s/^\%//;
  319       $checked = 'CHECKED'
  320     }
  321   if (defined($main::inputs_ref->{$name}) ) {
  322     if ($main::inputs_ref->{$name} eq $value) {
  323       $checked = 'CHECKED'
  324     } else {
  325       $checked = '';
  326     }
  327 
  328     }
  329 
  330   MODES(
  331     TeX => qq!\\item{$tag}\n!,
  332     Latex2HTML => qq!\\begin{rawhtml}\n<INPUT TYPE=RADIO NAME="$name" VALUE="$value" $checked>\\end{rawhtml}$tag!,
  333     HTML => qq!<INPUT TYPE=RADIO NAME="$name" VALUE="$value" $checked>$tag!
  334   );
  335 
  336 }
  337 
  338 sub NAMED_ANS_RADIO_OPTION { #deprecated
  339   &NAMED_ANS_RADIO_EXTENSION;
  340 }
  341 
  342 sub NAMED_ANS_RADIO_EXTENSION {
  343   my $name = shift;
  344   my $value = shift;
  345   my $tag =shift;
  346 
  347 
  348     my $checked = '';
  349     if ($value =~/^\%/) {
  350       $value =~ s/^\%//;
  351       $checked = 'CHECKED'
  352     }
  353   if (defined($main::inputs_ref->{$name}) ) {
  354     if ($main::inputs_ref->{$name} eq $value) {
  355       $checked = 'CHECKED'
  356     } else {
  357       $checked = '';
  358     }
  359 
  360     }
  361 
  362   MODES(
  363     TeX => qq!\\item{$tag}\n!,
  364     Latex2HTML => qq!\\begin{rawhtml}\n<INPUT TYPE=RADIO NAME="$name" VALUE="$value" $checked>\\end{rawhtml}$tag!,
  365     HTML => qq!<INPUT TYPE=RADIO NAME="$name" VALUE="$value" $checked>$tag!
  366   );
  367 
  368 }
  369 
  370 sub NAMED_ANS_RADIO_BUTTONS {
  371     my $name  =shift;
  372     my $value = shift;
  373     my $tag = shift;
  374 
  375 
  376   my @out = ();
  377   push(@out, NAMED_ANS_RADIO($name, $value,$tag));
  378   my @buttons = @_;
  379   while (@buttons) {
  380     $value = shift @buttons;  $tag = shift @buttons;
  381     push(@out, NAMED_ANS_RADIO_OPTION($name, $value,$tag));
  382   }
  383   (wantarray) ? @out : join(" ",@out);
  384 }
  385 sub ANS_RADIO {
  386   my $number = shift;
  387   my $value = shift;
  388   my $tag =shift;
  389     my $name = NEW_ANS_NAME($number);
  390   NAMED_ANS_RADIO($name,$value,$tag);
  391 }
  392 
  393 sub ANS_RADIO_OPTION {
  394   my $number = shift;
  395   my $value = shift;
  396   my $tag =shift;
  397 
  398 
  399     my $name = ANS_NUM_TO_NAME($number);
  400   NAMED_ANS_RADIO_OPTION($name,$value,$tag);
  401 }
  402 sub ANS_RADIO_BUTTONS {
  403     my $number  =shift;
  404     my $value = shift;
  405     my $tag = shift;
  406 
  407 
  408   my @out = ();
  409   push(@out, ANS_RADIO($number, $value,$tag));
  410   my @buttons = @_;
  411   while (@buttons) {
  412       $value = shift @buttons; $tag = shift @buttons;
  413     push(@out, ANS_RADIO_OPTION($number, $value,$tag));
  414   }
  415   (wantarray) ? @out : join(" ",@out);
  416 }
  417 
  418 sub NAMED_ANS_CHECKBOX {
  419   my $name = shift;
  420   my $value = shift;
  421     my $tag =shift;
  422     $name = RECORD_ANS_NAME($name);
  423 
  424     my $checked = '';
  425     if ($value =~/^\%/) {
  426       $value =~ s/^\%//;
  427       $checked = 'CHECKED'
  428     }
  429 
  430   if (defined($main::inputs_ref->{$name}) ) {
  431     if ($main::inputs_ref->{$name} eq $value) {
  432       $checked = 'CHECKED'
  433     }
  434     else {
  435       $checked = '';
  436     }
  437 
  438     }
  439 
  440   MODES(
  441     TeX => qq!\\item{$tag}\n!,
  442     Latex2HTML => qq!\\begin{rawhtml}\n<INPUT TYPE=CHECKBOX NAME="$name" VALUE="$value" $checked>\\end{rawhtml}$tag!,
  443     HTML => qq!<INPUT TYPE=CHECKBOX NAME="$name" VALUE="$value" $checked>$tag!
  444   );
  445 
  446 }
  447 
  448 sub NAMED_ANS_CHECKBOX_OPTION {
  449   my $name = shift;
  450   my $value = shift;
  451   my $tag =shift;
  452 
  453     my $checked = '';
  454     if ($value =~/^\%/) {
  455       $value =~ s/^\%//;
  456       $checked = 'CHECKED'
  457     }
  458 
  459   if (defined($main::inputs_ref->{$name}) ) {
  460     if ($main::inputs_ref->{$name} eq $value) {
  461       $checked = 'CHECKED'
  462     }
  463     else {
  464       $checked = '';
  465     }
  466 
  467     }
  468 
  469   MODES(
  470     TeX => qq!\\item{$tag}\n!,
  471     Latex2HTML => qq!\\begin{rawhtml}\n<INPUT TYPE=CHECKBOX NAME="$name" VALUE="$value" $checked>\\end{rawhtml}$tag!,
  472     HTML => qq!<INPUT TYPE=CHECKBOX NAME="$name" VALUE="$value" $checked>$tag!
  473   );
  474 
  475 }
  476 
  477 sub NAMED_ANS_CHECKBOX_BUTTONS {
  478     my $name  =shift;
  479     my $value = shift;
  480     my $tag = shift;
  481 
  482   my @out = ();
  483   push(@out, NAMED_ANS_CHECKBOX($name, $value,$tag));
  484 
  485   my @buttons = @_;
  486   while (@buttons) {
  487     $value = shift @buttons;  $tag = shift @buttons;
  488     push(@out, NAMED_ANS_CHECKBOX_OPTION($name, $value,$tag));
  489   }
  490 
  491   (wantarray) ? @out : join(" ",@out);
  492 }
  493 
  494 sub ANS_CHECKBOX {
  495   my $number = shift;
  496   my $value = shift;
  497   my $tag =shift;
  498     my $name = NEW_ANS_NAME($number);
  499 
  500   NAMED_ANS_CHECKBOX($name,$value,$tag);
  501 }
  502 
  503 sub ANS_CHECKBOX_OPTION {
  504   my $number = shift;
  505   my $value = shift;
  506   my $tag =shift;
  507     my $name = ANS_NUM_TO_NAME($number);
  508 
  509   NAMED_ANS_CHECKBOX_OPTION($name,$value,$tag);
  510 }
  511 
  512 sub ANS_CHECKBOX_BUTTONS {
  513     my $number  =shift;
  514     my $value = shift;
  515     my $tag = shift;
  516 
  517   my @out = ();
  518   push(@out, ANS_CHECKBOX($number, $value, $tag));
  519 
  520   my @buttons = @_;
  521   while (@buttons) {
  522     $value = shift @buttons;  $tag = shift @buttons;
  523     push(@out, ANS_CHECKBOX_OPTION($number, $value,$tag));
  524   }
  525 
  526   (wantarray) ? @out : join(" ",@out);
  527 }
  528 
  529 sub ans_rule {
  530   my $len = shift;     # gives the optional length of the answer blank
  531   $len    = 20 unless $len ;
  532   my $name = NEW_ANS_NAME(++$main::ans_rule_count);
  533   NAMED_ANS_RULE($name ,$len);
  534 }
  535 sub ans_rule_extension {
  536   my $len = shift;
  537     $len    = 20 unless $len ;
  538   my $name = NEW_ANS_NAME($main::ans_rule_count);  # don't update the answer name
  539   NAMED_ANS_RULE($name ,$len);
  540 }
  541 sub ans_radio_buttons {
  542   my $name  = NEW_ANS_NAME(++$main::ans_rule_count);
  543   my @radio_buttons = NAMED_ANS_RADIO_BUTTONS($name, @_);
  544 
  545   if ($displayMode eq 'TeX') {
  546     $radio_buttons[0] = "\n\\begin{itemize}\n" . $radio_buttons[0];
  547     $radio_buttons[$#radio_buttons] .= "\n\\end{itemize}\n";
  548   }
  549 
  550   (wantarray) ? @radio_buttons: join(" ", @radio_buttons);
  551 }
  552 
  553 #added 6/14/2000 by David Etlinger
  554 sub ans_checkbox {
  555   my $name = NEW_ANS_NAME( ++$main::ans_rule_count );
  556   my @checkboxes = NAMED_ANS_CHECKBOX_BUTTONS( $name, @_ );
  557 
  558   if ($displayMode eq 'TeX') {
  559     $checkboxes[0] = "\n\\begin{itemize}\n" . $checkboxes[0];
  560     $checkboxes[$#checkboxes] .= "\n\\end{itemize}\n";
  561   }
  562 
  563   (wantarray) ? @checkboxes: join(" ", @checkboxes);
  564 }
  565 
  566 
  567 ## define a version of ans_rule which will work inside TeX math mode or display math mode -- at least for tth mode.
  568 ## This is great for displayed fractions.
  569 ## This will not work with latex2HTML mode since it creates gif equations.
  570 
  571 sub tex_ans_rule {
  572   my $len = shift;
  573   $len    = 20 unless $len ;
  574     my $name = NEW_ANS_NAME(++$main::ans_rule_count);
  575     my $answer_rule = NAMED_ANS_RULE($name ,$len);  # we don't want to create three answer rules in different modes.
  576     my $out = MODES(
  577                      'TeX' => $answer_rule,
  578                      'Latex2HTML' => '\\fbox{Answer boxes cannot be placed inside typeset equations}',
  579                      'HTML_tth' => '\\begin{rawhtml} '. $answer_rule.'\\end{rawhtml}',
  580                      'HTML'     => $answer_rule
  581                    );
  582 
  583     $out;
  584 }
  585 sub tex_ans_rule_extension {
  586   my $len = shift;
  587   $len    = 20 unless $len ;
  588     my $name = NEW_ANS_NAME($main::ans_rule_count);
  589     my $answer_rule = NAMED_ANS_RULE($name ,$len);  # we don't want to create three answer rules in different modes.
  590     my $out = MODES(
  591                      'TeX' => $answer_rule,
  592                      'Latex2HTML' => '\fbox{Answer boxes cannot be placed inside typeset equations}',
  593                      'HTML_tth' => '\\begin{rawhtml} '. $answer_rule.'\\end{rawhtml}',
  594                      'HTML'     => $answer_rule
  595                    );
  596 
  597     $out;
  598 }
  599 # still needs some cleanup.
  600 sub NAMED_TEX_ANS_RULE {
  601     my $name = shift;
  602   my $len = shift;
  603   $len    = 20 unless $len ;
  604     my $answer_rule = NAMED_ANS_RULE($name ,$len);  # we don't want to create three answer rules in different modes.
  605     my $out = MODES(
  606                      'TeX' => $answer_rule,
  607                      'Latex2HTML' => '\\fbox{Answer boxes cannot be placed inside typeset equations}',
  608                      'HTML_tth' => '\\begin{rawhtml} '. $answer_rule.'\\end{rawhtml}',
  609                      'HTML'     => $answer_rule
  610                    );
  611 
  612     $out;
  613 }
  614 sub NAMED_TEX_ANS_RULE_EXTENSION {
  615   my $name = shift;
  616   my $len = shift;
  617   $len    = 20 unless $len ;
  618     my $answer_rule = NAMED_ANS_RULE_EXTENSION($name ,$len);  # we don't want to create three answer rules in different modes.
  619     my $out = MODES(
  620                      'TeX' => $answer_rule,
  621                      'Latex2HTML' => '\fbox{Answer boxes cannot be placed inside typeset equations}',
  622                      'HTML_tth' => '\\begin{rawhtml} '. $answer_rule.'\\end{rawhtml}',
  623                      'HTML'     => $answer_rule
  624                    );
  625 
  626     $out;
  627 }
  628 sub ans_box {
  629   my $row = shift;
  630   my $col =shift;
  631   $row = 5 unless $row;
  632   $col = 80 unless $col;
  633   my $name = NEW_ANS_NAME(++$main::ans_rule_count);
  634   NAMED_ANS_BOX($name ,$row,$col);
  635 }
  636 
  637 #this is legacy code; use ans_checkbox instead
  638 sub checkbox {
  639   my %options = @_;
  640   qq!<INPUT TYPE="checkbox" NAME="$options{'-name'}" VALUE="$options{'-value'}">$options{'-label'}!
  641 }
  642 
  643 
  644 sub NAMED_POP_UP_LIST {
  645     my $name = shift;
  646   my @list = @_;
  647   $name = RECORD_ANS_NAME($name);   # record answer name
  648     my $answer_value = '';
  649   $answer_value = ${$main::inputs_ref}{$name} if defined(${$main::inputs_ref}{$name});
  650   my $out = "";
  651   if ($displayMode eq "HTML" or $displayMode eq "HTML_tth") {
  652     $out = qq!<SELECT NAME = "$name" SIZE=1> \n!;
  653     my $i;
  654     foreach ($i=0; $i< @list; $i=$i+2) {
  655       my $select_flag = ($list[$i] eq $answer_value) ? "SELECTED" : "";
  656       $out .= qq!<OPTION $select_flag VALUE ="$list[$i]" > $list[$i+1]  </OPTION>\n!;
  657     };
  658     $out .= " </SELECT>\n";
  659   } elsif ( $displayMode eq "Latex2HTML") {
  660     $out = qq! \\begin{rawhtml}<SELECT NAME = "$name" SIZE=1> \\end{rawhtml} \n !;
  661     my $i;
  662     foreach ($i=0; $i< @list; $i=$i+2) {
  663       my $select_flag = ($list[$i] eq $answer_value) ? "SELECTED" : "";
  664       $out .= qq!\\begin{rawhtml}<OPTION $select_flag VALUE ="$list[$i]" > $list[$i+1]  </OPTION>\\end{rawhtml}\n!;
  665     };
  666     $out .= " \\begin{rawhtml}</SELECT>\\end{rawhtml}\n";
  667   } elsif ( $displayMode eq "TeX") {
  668       $out .= "\\fbox{?}";
  669   }
  670 
  671 }
  672 
  673 sub pop_up_list {
  674   my @list = @_;
  675   my $name = NEW_ANS_NAME(++$main::ans_rule_count);  # get new answer name
  676   NAMED_POP_UP_LIST($name, @list);
  677 }
  678 
  679 # end answer blank macros
  680 
  681 =head2 Hints and solutions macros
  682 
  683   solution('text','text2',...);
  684   SOLUTION('text','text2',...);   # equivalent to TEXT(solution(...));
  685 
  686   hint('text', 'text2', ...);
  687   HINT('text', 'text2',...);      # equivalent to TEXT("$BR$HINT" . hint(@_) . "$BR") if hint(@_);
  688 
  689 Solution prints its concatenated input when the check box named 'ShowSol' is set and
  690 the time is after the answer date.  The check box 'ShowSol' is visible only after the
  691 answer date or when the problem is viewed by a professor.
  692 
  693 $envir{'displaySolutionsQ'} is set to 1 when a solution is to be displayed.
  694 
  695 Hints are shown only after the number of attempts is greater than $:showHint
  696 ($main::showHint defaults to 1) and the check box named 'ShowHint' is set. The check box
  697 'ShowHint' is visible only after the number of attempts is greater than $main::showHint.
  698 
  699 $envir{'displayHintsQ'} is set to 1 when a hint is to be displayed.
  700 
  701 
  702 =cut
  703 
  704 
  705 
  706 #   solution prints its input when $displaySolutionsQ is set.
  707 #   use as TEXT(solution("blah, blah");
  708 #   \$solutionExists
  709 #   is passed to processProblem which displays a "show Solution" button
  710 #   when a solution is available for viewing
  711 
  712 
  713 sub solution {
  714   my @in = @_;
  715   my $out = '';
  716   $main::solutionExists =1;
  717   if ($envir{'displaySolutionsQ'}) {$out = join(' ',@in);}
  718     $out;
  719 }
  720 
  721 
  722 sub SOLUTION {
  723   TEXT( solution(@_)) ;
  724 }
  725 
  726 
  727 
  728 sub hint {
  729     my @in = @_;
  730   my $out = '';
  731 
  732   $main::hintExists =1;
  733     $main::numOfAttempts = 0 unless defined($main::numOfAttempts);
  734 
  735   if ($main::displayMode eq 'TeX')   {
  736     $out = '';  # do nothing since hints are not available for download
  737   } elsif (($envir{'displayHintsQ'}) and ($main::numOfAttempts >= $main::showHint))
  738 
  739    ## the second test above prevents a hint being shown if a doctored form is submitted
  740 
  741   {$out = join(' ',@in);}    # show hint
  742 
  743   $out ;
  744 }
  745 
  746 
  747 sub HINT {
  748     TEXT("$main::BR" . hint(@_) . "$main::BR") if hint(@_);
  749 }
  750 
  751 
  752 
  753 # End hints and solutions macros
  754 #################################
  755 
  756 # Produces a random number between $begin and $end with increment 1.
  757 # You do not have to worry about integer or floating point types.
  758 
  759 =head2 Pseudo-random number generator
  760 
  761   Usage:
  762   random(0,5,.1)      # produces a random number between 0 and 5 in increments of .1
  763   non_zero_random(0,5,.1) # gives a non-zero random number
  764 
  765   list_random(2,3,5,6,7,8,10) # produces random value from the list
  766         list_random(2,3, (5..8),10) # does the same thing
  767 
  768   SRAND(seed)     # resets the main random generator -- use very cautiously
  769 
  770 
  771 SRAND(time) will create a different problem everytime it is called.  This makes it difficult
  772 to check the answers :-).
  773 
  774 SRAND($envir{'inputs_ref'}->{'key'} ) will create a different problem for each login session.
  775 This is probably what is desired.
  776 
  777 =cut
  778 
  779 
  780 sub random  {
  781   my ($begin, $end, $incr) = @_;
  782   $main::PG_random_generator->random($begin,$end,$incr);
  783 }
  784 
  785 
  786 sub non_zero_random { ##gives a non-zero random number
  787   my (@arguments)=@_;
  788   my $a=0;
  789   my $i=100; #safety counter
  790   while ($a==0 && ( 0 < $i-- ) ) {
  791     $a=random(@arguments);
  792   }
  793   $a;
  794 }
  795 
  796 sub list_random {
  797         my(@li) = @_;
  798         return $li[random(1,scalar(@li))-1];
  799 }
  800 
  801 sub SRAND { # resets the main random generator -- use cautiously
  802     my $seed = shift;
  803   $main::PG_random_generator -> srand($seed);
  804 }
  805 
  806 # display macros
  807 
  808 =head2 Display Macros
  809 
  810 These macros produce different output depending on the display mode being used to show
  811 the problem on the screen, or whether the problem is being converted to TeX to produce
  812 a hard copy output.
  813 
  814   MODES   ( TeX =>        "Output this in TeX mode",
  815             HTML =>       "output this in HTML mode",
  816             HTML_tth =>   "output this in HTML_tth mode",
  817             Latex2HTML => "output this in Latex2HTML mode",
  818            )
  819 
  820   TEX     (tex_version, html_version) #obsolete
  821 
  822   M3      (tex_version, latex2html_version, html_version) #obsolete
  823 
  824 
  825 
  826 =cut
  827 
  828 
  829 sub TEX {
  830   my ($tex, $html ) = @_;
  831   MODES(TeX => $tex, HTML => $html, HTML_tth => $html);
  832 }
  833 
  834 
  835 sub M3 {
  836   my($tex,$l2h,$html) = @_;
  837   MODES(TeX => $tex, Latex2HTML => $l2h, HTML => $html, HTML_tth => $html);
  838 }
  839 
  840 # This replaces M3.  You can add new modes at will to this one.
  841 
  842 sub MODES {
  843   my %options = @_;
  844   return $options{$displayMode}
  845              if defined( $options{$displayMode} );
  846 
  847   # default searches.
  848   if ($displayMode eq "Latex2HTML") {
  849     return $options{TeX}
  850              if defined( $options{TeX} );
  851       return $options{HTML}
  852              if defined( $options{HTML} );
  853       die " ERROR in using MODES: 'HTML' and 'TeX' options not defined for 'Latex2HTML'";
  854   }
  855 
  856   if ($displayMode eq "HTML_tth") {
  857     return $options{HTML}
  858              if defined( $options{HTML} );
  859       die " ERROR in using MODES: 'HTML' option not defined for HTML_tth";
  860 
  861   }
  862 
  863   # trap undefined errors
  864   die "ERROR in defining MODES:  Can't find |$displayMode| among
  865            available options:" . join(" ", keys(%options) )
  866            . " file " . __FILE__ ." line " . __LINE__."\n\n";
  867 
  868 }
  869 
  870 
  871 # end display macros
  872 
  873 
  874 =head2  Display constants
  875 
  876   @ALPHABET       ALPHABET()      capital letter alphabet -- ALPHABET[0] = 'A'
  877   $PAR        PAR()       paragraph character (\par or <p>)
  878   $BR             BR()        line break character
  879   $LQ         LQ()        left double quote
  880   $RQ         RQ()        right double quote
  881   $BM         BM()        begin math
  882   $EM         EM()        end math
  883   $BDM        BDM()       begin display math
  884   $EDM        EDM()       end display math
  885   $LTS        LTS()       strictly less than
  886   $GTS        GTS()       strictly greater than
  887   $LTE        LTE()       less than or equal
  888   $GTE        GTE()       greater than or equal
  889   $BEGIN_ONE_COLUMN BEGIN_ONE_COLUMN()  begin one-column mode
  890   $END_ONE_COLUMN   END_ONE_COLUMN()  end one-column mode
  891   $SOL        SOLUTION_HEADING()  solution headline
  892   $SOLUTION     SOLUTION_HEADING()  solution headline
  893   $HINT       HINT_HEADING()    hint headline
  894   $US         US()        underscore character
  895   $SPACE        SPACE()       space character (tex and latex only)
  896   $BBOLD        BBOLD()       begin bold typeface
  897   $EBOLD        EBOLD()       end bold typeface
  898   $BITALIC        BITALIC()       begin italic typeface
  899   $EITALIC        EITALIC()       end italic typeface
  900   $BCENTER        BCENTER()       begin centered environment
  901   $ECENTER        ECENTER()       end centered environment
  902   $HR         HR()        horizontal rule
  903   $LBRACE       LBRACE()      left brace
  904   $LB         LB ()       left brace
  905   $RBRACE       RBRACE()      right brace
  906   $RB         RB ()       right brace
  907   $DOLLAR       DOLLAR()      a dollar sign
  908   $PERCENT      PERCENT()     a percent sign
  909   $CARET        CARET()       a caret sign
  910   $PI         PI()        the number pi
  911   $E          E()         the number e
  912 
  913 =cut
  914 
  915 
  916 
  917 
  918 
  919 # A utility variable.  Notice that "B"=$ALPHABET[1] and
  920 # "ABCD"=@ALPHABET[0..3].
  921 
  922 sub ALPHABET  {
  923   ('A'..'ZZ')[@_];
  924 }
  925 
  926 ###############################################################
  927 # Some constants which are different in tex and in HTML
  928 # The order of arguments is TeX, Latex2HTML, HTML
  929 sub PAR { MODES( TeX => '\\par ',Latex2HTML => '\\par ',HTML => '<P>' ); };
  930 sub BR { MODES( TeX => '\\par\\noindent ',Latex2HTML => '\\par\\noindent ',HTML => '<BR>'); };
  931 sub LQ { MODES( TeX => "``", Latex2HTML =>   '"',  HTML =>  '&quot;' ); };
  932 sub RQ { MODES( TeX => "''", Latex2HTML =>   '"',   HTML =>  '&quot;' ); };
  933 sub BM { MODES(TeX => '\\(', Latex2HTML => '\\(', HTML =>  ''); };  # begin math mode
  934 sub EM { MODES(TeX => '\\)', Latex2HTML => '\\)', HTML => ''); };  # end math mode
  935 sub BDM { MODES(TeX => '\\[', Latex2HTML =>   '\\[', HTML =>   '<P ALIGN=CENTER>'); };  #begin displayMath mode
  936 sub EDM { MODES(TeX => '\\]',  Latex2HTML =>  '\\]', HTML => '</P>'); };              #end displayMath mode
  937 sub LTS { MODES(TeX => ' < ', Latex2HTML => ' \\lt ',  HTML =>   '&lt;'); };
  938 sub GTS {MODES(TeX => ' > ', Latex2HTML => ' \\gt ',  HTML =>    '&gt;'); };
  939 sub LTE { MODES(TeX => ' \\le ', Latex2HTML =>  ' \\le ',  HTML => '&lt;=' ); };
  940 sub GTE { MODES(TeX => ' \\ge ',  Latex2HTML => ' \\ge ',  HTML =>  '&gt;'); };
  941 sub BEGIN_ONE_COLUMN { MODES(TeX => " \\end{multicols}\n",  Latex2HTML => " ", HTML =>   " "); };
  942 sub END_ONE_COLUMN { MODES(TeX =>
  943               " \\begin{multicols}{2}\n\\columnwidth=\\linewidth\n",
  944                             Latex2HTML => ' ', HTML => ' ');
  945 
  946 };
  947 sub SOLUTION_HEADING { MODES( TeX => '\\par {\\bf Solution:}',
  948                  Latex2HTML => '\\par {\\bf Solution:}',
  949                HTML =>  '<P><B>Solution:</B>');
  950               };
  951 sub HINT_HEADING { MODES( TeX => "\\par {\\bf Hint:}", Latex2HTML => "\\par {\\bf Hint:}", HTML => "<P><B>Hint:</B>"); };
  952 sub US { MODES(TeX => '\\_', Latex2HTML => '\\_', HTML => '_');};  # underscore, e.g. file${US}name
  953 sub SPACE { MODES(TeX => '\\ ',  Latex2HTML => '\\ ', HTML => '&nbsp;');};  # force a space in latex, doesn't force extra space in html
  954 sub BBOLD { MODES(TeX => '{\\bf ',  Latex2HTML => '{\\bf ', HTML => '<B>'); };
  955 sub EBOLD { MODES( TeX => '}', Latex2HTML =>  '}',HTML =>  '</B>'); };
  956 sub BITALIC { MODES(TeX => '{\\it ',  Latex2HTML => '{\\it ', HTML => '<I>'); };
  957 sub EITALIC { MODES(TeX => '} ',  Latex2HTML => '} ', HTML => '</I>'); };
  958 sub BCENTER { MODES(TeX => '\\begin{center} ',  Latex2HTML => ' \\begin{rawhtml} <div align="center"> \\end{rawhtml} ', HTML => '<div align="center">'); };
  959 sub ECENTER { MODES(TeX => '\\end{center} ',  Latex2HTML => ' \\begin{rawhtml} </div> \\end{rawhtml} ', HTML => '</div>'); };
  960 sub HR { MODES(TeX => '\\par\\hrulefill\\par ', Latex2HTML => '\\begin{rawhtml} <HR> \\end{rawhtml}', HTML =>  '<HR>'); };
  961 sub LBRACE { MODES( TeX => '\{', Latex2HTML =>   '\\lbrace',  HTML =>  '\{' , HTML_tth=> '\\lbrace' ); };
  962 sub RBRACE { MODES( TeX => '\}', Latex2HTML =>   '\\rbrace',  HTML =>  '\}' , HTML_tth=> '\\rbrace',); };
  963 sub LB { MODES( TeX => '\{', Latex2HTML =>   '\\lbrace',  HTML =>  '\{' , HTML_tth=> '\\lbrace' ); };
  964 sub RB { MODES( TeX => '\}', Latex2HTML =>   '\\rbrace',  HTML =>  '\}' , HTML_tth=> '\\rbrace',); };
  965 sub DOLLAR { MODES( TeX => '\\$', Latex2HTML => '\\$', HTML => '$' ); };
  966 sub PERCENT { MODES( TeX => '\\%', Latex2HTML => '\\%', HTML => '%' ); };
  967 sub CARET { MODES( TeX => '\\verb+^+', Latex2HTML => '\\verb+^+', HTML => '^' ); };
  968 sub PI {4*atan2(1,1);};
  969 sub E {exp(1);};
  970 
  971 ###############################################################
  972 ## Evaluation macros
  973 
  974 
  975 =head2 TEXT macros
  976 
  977   Usage:
  978     TEXT(@text);
  979 
  980 This is the simplest way to print text from a problem.  The strings in the array C<@text> are concatenated
  981 with spaces between them and printed out in the text of the problem.  The text is not processed in any other way.
  982 C<TEXT> is defined in PG.pl.
  983 
  984   Usage:
  985     BEGIN_TEXT
  986       text.....
  987     END_TEXT
  988 
  989 This is the most common way to enter text into the problem.  All of the text between BEGIN_TEXT and END_TEXT
  990 is processed by the C<EV3> macro described below and then printed using the C<TEXT> command.  The two key words
  991 must appear on lines by themselves.  The preprocessing that makes this construction work is done in F<PGtranslator.pm>.
  992 See C<EV3> below for details on the processing.
  993 
  994 
  995 =cut
  996 
  997 =head2 Evaluation macros
  998 
  999 =head3 EV3
 1000 
 1001         TEXT(EV3("This is a formulat \( \int_0^5 x^2 \, dx \) ");
 1002         TEXT(EV3(@text));
 1003 
 1004     TEXT(EV3(<<'END_TEXT'));
 1005       text stuff...
 1006     END_TEXT
 1007 
 1008 
 1009 The BEGIN_TEXT/END_TEXT construction is translated into the construction above by PGtranslator.pm.  END_TEXT must appear
 1010 on a line by itself and be left justified.  (The << construction is known as a "here document" in UNIX and in PERL.)
 1011 
 1012 The single quotes around END_TEXT mean that no automatic interpolation of variables takes place in the text.
 1013 Using EV3 with strings which have been evaluated by double quotes may lead to unexpected results.
 1014 
 1015 
 1016 The evaluation macro E3 first evaluates perl code inside the braces:  C<\{  code \}>.
 1017 Any perl statment can be put inside the braces.  The
 1018 result of the evaluation (i.e. the last statement evaluated) replaces the C<\{ code \}> construction.
 1019 
 1020 Next interpolation of all variables (e.g. C<$var or @array> ) is performed.
 1021 
 1022 Then mathematical formulas in TeX are evaluated within the
 1023 C<\(  tex math mode \)> and
 1024 C<\[ tex display math mode \] >
 1025 constructions, in that order:
 1026 
 1027 =head3 FEQ
 1028 
 1029   FEQ($string);   # processes and outputs the string
 1030 
 1031 
 1032 The mathematical formulas are run through the macro C<FEQ> (Format EQuations) which performs
 1033 several substitutions (see below).
 1034 In C<HTML_tth> mode the resulting code is processed by tth to obtain an HTML version
 1035 of the formula. (In the future processing by WebEQ may be added here as another option.)
 1036 The Latex2HTML mode does nothing
 1037 at this stage; it creates the entire problem before running it through
 1038 TeX and creating the GIF images of the equations.
 1039 
 1040 The resulting string is output (and usually fed into TEXT to be printed in the problem).
 1041 
 1042   Usage:
 1043 
 1044     $string2 = FEQ($string1);
 1045 
 1046 This is a filter which is used to format equations by C<EV2> and C<EV3>, but can also be used on its own.  It is best
 1047 understood with an example.
 1048 
 1049     $string1 = "${a}x^2 + ${b}x + {$c:%.1f}"; $a = 3;, $b = -2; $c = -7.345;
 1050 
 1051 when interpolated becomes:
 1052 
 1053     $string1 = '3x^2 + -2x + {-7.345:%0.1f}
 1054 
 1055 FEQ first changes the number of decimal places displayed, so that the last term becomes -7.3 Then it removes the
 1056 extraneous plus and minus signs, so that the final result is what you want:
 1057 
 1058     $string2 = '3x^2 - 2x -7.3';
 1059 
 1060 (The %0.1f construction
 1061 is the same formatting convention used by Perl and nearly identical to the one used by the C printf statement. Some common
 1062 usage:  %0.3f 3 decimal places, fixed notation; %0.3e 3 significant figures exponential notation; %0.3g uses either fixed
 1063 or exponential notation depending on the size of the number.)
 1064 
 1065 Two additional legacy formatting constructions are also supported:
 1066 
 1067 C<?{$c:%0.3f} > will give a number with 3 decimal places and a negative
 1068 sign if the number is negative, no sign if the number is positive.
 1069 
 1070 C<!{$c:%0.3f}> determines the sign and prints it
 1071 whether the number is positive or negative.
 1072 
 1073 =head3 EV2
 1074 
 1075     TEXT(EV2(@text));
 1076 
 1077     TEXT(EV2(<<END_OF_TEXT));
 1078       text stuff...
 1079     END_OF_TEXT
 1080 
 1081 This is a precursor to EV3.  In this case the constants are interpolated first, before the evaluation of the \{ ...code...\}
 1082 construct. This can lead to unexpected results.  For example C<\{ join(" ", @text) \}> with C<@text = ("Hello","World);> becomes,
 1083 after interpolation, C<\{ join(" ",Hello World) \}> which then causes an error when evaluated because Hello is a bare word.
 1084 C<EV2> can still be useful if you allow for this, and in particular it works on double quoted strings, which lead to
 1085 unexpected results with C<EV3>. Using single quoted strings with C<EV2> may lead to unexpected results.
 1086 
 1087 The unexpected results have to do with the number of times backslashed constructions have to be escaped. It's quite messy.  For
 1088 more details get a good Perl book and then read the code. :-)
 1089 
 1090 
 1091 
 1092 
 1093 =cut
 1094 
 1095 
 1096 sub ev_substring {
 1097     my $string      = shift;
 1098   my $start_delim = shift;
 1099   my $end_delim   = shift;
 1100   my $actionRef   = shift;
 1101   my ($eval_out,$PG_eval_errors,$PG_full_error_report)=();
 1102     my $out = "";
 1103     while ($string) {
 1104         if ($string =~ /\Q$start_delim\E/s) {
 1105        #print "$start_delim $end_delim evaluating_substring=$string<BR>";
 1106         $string =~ s/^(.*?)\Q$start_delim\E//s;  # get string up to next \{ ---treats string as a single line, ignoring returns
 1107         $out .= $1;
 1108        #print "$start_delim $end_delim substring_out=$out<BR>";
 1109         $string =~ s/^(.*?)\Q$end_delim\E//s;  # get perl code up to \} ---treats string as a single line,  ignoring returns
 1110            #print "$start_delim $end_delim evaluate_string=$1<BR>";
 1111         ($eval_out,$PG_eval_errors,$PG_full_error_report) = &$actionRef($1);
 1112         $eval_out = "$start_delim $eval_out $end_delim" if $PG_full_error_report;
 1113         $out = $out . $eval_out;
 1114        #print "$start_delim $end_delim new substring_out=$out<BR><p><BR>";
 1115         $out .="$main::PAR ERROR $0 in ev_substring, PGbasicmacros.pl:$main::PAR <PRE>  $@ </PRE>$main::PAR" if $@;
 1116         }
 1117       else {
 1118         $out .= $string;  # flush the last part of the string
 1119         last;
 1120         }
 1121 
 1122       }
 1123   $out;
 1124 }
 1125 sub  safe_ev {
 1126     my ($out,$PG_eval_errors,$PG_full_error_report) = &old_safe_ev;   # process input by old_safe_ev first
 1127     $out =~s/\\/\\\\/g;   # protect any new backslashes introduced.
 1128   ($out,$PG_eval_errors,$PG_full_error_report)
 1129 }
 1130 
 1131 sub  old_safe_ev {
 1132     my $in = shift;
 1133     my   ($out,$PG_eval_errors,$PG_full_error_report) = PG_restricted_eval("$in;");
 1134     # the addition of the ; seems to provide better error reporting
 1135     if ($PG_eval_errors) {
 1136       my @errorLines = split("\n",$PG_eval_errors);
 1137     #$out = "<PRE>$main::PAR % ERROR in $0:old_safe_ev, PGbasicmacros.pl: $main::PAR % There is an error occuring inside evaluation brackets \\{ ...code... \\} $main::BR % somewhere in an EV2 or EV3 or BEGIN_TEXT block. $main::BR % Code evaluated:$main::BR $in $main::BR % $main::BR % $errorLines[0]\n % $errorLines[1]$main::BR % $main::BR % $main::BR </PRE> ";
 1138     warn " ERROR in old_safe_ev, PGbasicmacros.pl: <PRE>
 1139      ## There is an error occuring inside evaluation brackets \\{ ...code... \\}
 1140      ## somewhere in an EV2 or EV3 or BEGIN_TEXT block.
 1141      ## Code evaluated:
 1142      ## $in
 1143      ##" .join("\n     ", @errorLines). "
 1144      ##</PRE>$main::BR
 1145      ";
 1146      $out ="$main::PAR $main::BBOLD  $in $main::EBOLD $main::PAR";
 1147 
 1148 
 1149   }
 1150 
 1151   ($out,$PG_eval_errors,$PG_full_error_report);
 1152 }
 1153 
 1154 sub FEQ   {    # Format EQuations
 1155   my $in = shift;
 1156    # formatting numbers -- the ?{} and !{} constructions
 1157   $in =~s/\?\s*\{([.\-\$\w\d]+):?([%.\da-z]*)\}/${ \( &sspf($1,$2) )}/g;
 1158   $in =~s/\!\s*\{([.\-\$\w\d]+):?([%.\da-z]*)\}/${ \( &spf($1,$2) )}/g;
 1159 
 1160   # more formatting numbers -- {number:format} constructions
 1161   $in =~ s/\{(\s*[\+\-\d\.]+[eE]*[\+\-]*\d*):(\%\d*.\d*\w)}/${ \( &spf($1,$2) )}/g;
 1162   $in =~ s/\+\s*\-/ - /g;
 1163   $in =~ s/\-\s*\+/ - /g;
 1164   $in =~ s/\+\s*\+/ + /g;
 1165   $in =~ s/\-\s*\-/ + /g;
 1166   $in;
 1167 }
 1168 
 1169 sub math_ev3 {
 1170   my $in = shift; #print "in=$in<BR>";
 1171   my ($out,$PG_eval_errors,$PG_full_error_report);
 1172   $in = FEQ($in);
 1173   $in =~ s/%/\\%/g;   #  % causes trouble in TeX and HTML_tth it usually (always?) indicates an error, not comment
 1174   return("$main::BM $in $main::EM") unless ($displayMode eq 'HTML_tth');
 1175   $in = "\\(" . $in . "\\)";
 1176   $out = tth($in);
 1177   ($out,$PG_eval_errors,$PG_full_error_report);
 1178 
 1179 }
 1180 
 1181 sub display_math_ev3 {
 1182   my $in = shift; #print "in=$in<BR>";
 1183   my ($out,$PG_eval_errors,$PG_full_error_report);
 1184   $in = FEQ($in);
 1185   $in =~ s/%/\\%/g;
 1186   return("$main::BDM $in $main::EDM") unless $displayMode eq 'HTML_tth' ;
 1187   $in = "\\[" . $in . "\\]";
 1188   $out =tth($in);
 1189   ($out,$PG_eval_errors,$PG_full_error_report);
 1190 }
 1191 
 1192 sub EV2 {
 1193   my $string = join(" ",@_);
 1194   # evaluate code inside of \{  \}  (no nesting allowed)
 1195     $string = ev_substring($string,"\\{","\\}",\&old_safe_ev);
 1196     $string = ev_substring($string,"\\<","\\>",\&old_safe_ev);
 1197   $string = ev_substring($string,"\\(","\\)",\&math_ev3);
 1198   $string = ev_substring($string,"\\[","\\]",\&display_math_ev3);
 1199   # macros for displaying math
 1200   $string =~ s/\\\(/$main::BM/g;
 1201   $string =~ s/\\\)/$main::EM/g;
 1202   $string =~ s/\\\[/$main::BDM/g;
 1203   $string =~ s/\\\]/$main::EDM/g;
 1204   $string;
 1205 }
 1206 
 1207 sub EV3{
 1208   my $string = join(" ",@_);
 1209   # evaluate code inside of \{  \}  (no nesting allowed)
 1210     $string = ev_substring($string,"\\\\{","\\\\}",\&safe_ev);  # handles \{ \} in single quoted strings of PG files
 1211   # interpolate variables
 1212   my ($evaluated_string,$PG_eval_errors,$PG_full_errors) = PG_restricted_eval("<<END_OF_EVALUATION_STRING\n$string\nEND_OF_EVALUATION_STRING\n");
 1213   if ($PG_eval_errors) {
 1214       my @errorLines = split("\n",$PG_eval_errors);
 1215       $string =~ s/</&lt;/g; $string =~ s/>/&gt;/g;
 1216     $evaluated_string = "<PRE>$main::PAR % ERROR in $0:EV3, PGbasicmacros.pl: $main::PAR % There is an error occuring in the following code:$main::BR $string $main::BR % $main::BR % $errorLines[0]\n % $errorLines[1]$main::BR % $main::BR % $main::BR </PRE> ";
 1217     $@="";
 1218   }
 1219   $string = $evaluated_string;
 1220   $string = ev_substring($string,"\\(","\\)",\&math_ev3);
 1221     $string = ev_substring($string,"\\[","\\]",\&display_math_ev3);
 1222   $string;
 1223 }
 1224 
 1225 =head2 Formatting macros
 1226 
 1227   beginproblem()  # generates text listing number and the point value of
 1228                   # the problem. It will also print the file name containing
 1229                   # the problem for users listed in the PRINT_FILE_NAMES_FOR PG_environment
 1230                   # variable.
 1231   OL(@array)      # formats the array as an Ordered List ( <OL> </OL> ) enumerated by letters.
 1232 
 1233   htmlLink($url, $text)
 1234                   # Places a reference to the URL with the specified text in the problem.
 1235                   # A common usage is \{ htmlLink(alias('prob1_help.html') \}, 'for help')
 1236                   # where alias finds the full address of the prob1_help.html file in the same directory
 1237                   # as the problem file
 1238   appletLink($url, $parameters)
 1239                   # For example
 1240                   # appletLink(q!  archive="http: //webwork.math.rochester.edu/gage/xFunctions/xFunctions.zip"
 1241                                   code="xFunctionsLauncher.class"  width=100 height=14!,
 1242                   " parameter text goes here")
 1243                   # will link to xFunctions.
 1244 
 1245   low level:
 1246 
 1247   spf($number, $format)   # prints the number with the given format
 1248   sspf($number, $format)  # prints the number with the given format, always including a sign.
 1249   protect_underbar($string) # protects the underbar (class_name) in strings which may have to pass through TeX.
 1250 
 1251 =cut
 1252 
 1253 sub beginproblem {
 1254   my $out = "";
 1255     my $TeXFileName = protect_underbar($main::fileName);
 1256     my $l2hFileName = protect_underbar($main::fileName);
 1257   my %inlist;
 1258   my $points ='pts';
 1259   $points = 'pt' if $main::problemValue == 1;
 1260   ##    Prepare header for the problem
 1261   grep($inlist{$_}++,@{ $envir{'PRINT_FILE_NAMES_FOR'} });
 1262   if ( defined($inlist{$main::studentLogin}) and ($inlist{$main::studentLogin} > 0) ) {
 1263     $out = &M3("\n\n\\medskip\\hrule\\smallskip\\par{\\bf ${main::probNum}.{\\footnotesize ($main::problemValue $points) $TeXFileName}}\\newline ",
 1264     " \\begin{rawhtml} ($main::problemValue $points) <B>$l2hFileName</B><BR>\\end{rawhtml}",
 1265      "($main::problemValue $points) <B>$main::fileName</B><BR>"
 1266        );
 1267   } else {
 1268     $out = &M3("\n\n\\smallskip\\hrule\\smallskip\\par{\\bf ${main::probNum}.}($main::problemValue $points) ",
 1269     "($main::problemValue $points) ",
 1270      "($main::problemValue $points) "
 1271        );
 1272   }
 1273   $out;
 1274 
 1275 }
 1276 
 1277 # kludge to clean up path names
 1278             ## allow underscore character in set and section names and also allows line breaks at /
 1279 sub protect_underbar {
 1280     my $in = shift;
 1281     if ($displayMode eq 'TeX')  {
 1282 
 1283         $in =~ s|_|\\\_|g;
 1284         $in =~ s|/|\\\-/|g;  # allows an optional hyphenation of the path (in tex)
 1285     }
 1286     $in;
 1287 }
 1288 
 1289 
 1290 # An example of a macro which prints out a list (with letters)
 1291 sub OL {
 1292   my(@array) = @_;
 1293   my $i = 0;
 1294   my  $out=   &M3(
 1295           "\\begin{enumerate}\n",
 1296           " \\begin{rawhtml} <OL TYPE=\"A\" VALUE=\"1\"> \\end{rawhtml} ",
 1297           "<OL TYPE=\"A\" VALUE=\"1\">\n"
 1298           ) ;
 1299   my $elem;
 1300   foreach $elem (@array) {
 1301     $out .= &M3(
 1302           "\\item[$main::ALPHABET[$i].] $elem\n",
 1303           " \\begin{rawhtml} <LI> \\end{rawhtml} $elem  ",
 1304           "<LI> $elem\n"
 1305           ) ;
 1306     $i++;
 1307   }
 1308   $out .= &M3(
 1309         "\\end{enumerate}\n",
 1310         " \\begin{rawhtml} </OL>\n \\end{rawhtml} ",
 1311         "</OL>\n"
 1312         ) ;
 1313 }
 1314 
 1315 sub htmlLink {
 1316   my $url = shift;
 1317   my $text = shift;
 1318   my $options = shift;
 1319   $options = "" unless defined($options);
 1320   return "${main::BBOLD}[ broken link:  $text ] ${main::EBOLD}" unless defined($url);
 1321   M3( "{\\bf \\underline{$text}  }",
 1322       "\\begin{rawhtml} <A HREF=\"$url\" $options> $text </A>\\end{rawhtml}",
 1323       "<A HREF=\"$url\" $options> $text </A>"
 1324       );
 1325 }
 1326 sub appletLink {
 1327   my $url = shift;
 1328   my $options = shift;
 1329   $options = "" unless defined($options);
 1330   M3( "{\\bf \\underline{APPLET}  }",
 1331       "\\begin{rawhtml} <APPLET $url> $options </APPLET>\\end{rawhtml}",
 1332       "<APPLET $url> $options </APPLET>"
 1333       );
 1334 }
 1335 sub spf {
 1336   my($number,$format) = @_;  # attention, the order of format and number are reversed
 1337   $format = "%4.3g" unless $format;   # default value for format
 1338   sprintf($format, $number);
 1339   }
 1340 sub sspf {
 1341   my($number,$format) = @_;  # attention, the order of format and number are reversed
 1342   $format = "%4.3g" unless $format;   # default value for format
 1343   my $sign = $number>=0 ? " + " : " - ";
 1344   $number = $number>=0 ? $number : -$number;
 1345   $sign .sprintf($format, $number);
 1346   }
 1347 
 1348 =head2  Sorting and other list macros
 1349 
 1350 
 1351 
 1352   Usage:
 1353   lex_sort(@list);   # outputs list in lexigraphic (alphabetical) order
 1354   num_sort(@list);   # outputs list in numerical order
 1355   uniq( @list);      # outputs a list with no duplicates.  Order is unspecified.
 1356 
 1357   PGsort( \&sort_subroutine, @list);
 1358   # &sort_subroutine defines order. It's output must be -1,0 or 1.
 1359 
 1360 =cut
 1361 
 1362 #  uniq gives unique elements of a list:
 1363  sub uniq {
 1364    my (@in) =@_;
 1365    my %temp = ();
 1366    while (@in) {
 1367           $temp{shift(@in)}++;
 1368       }
 1369    my @out =  keys %temp;  # sort is causing trouble with Safe.??
 1370    @out;
 1371 }
 1372 
 1373 sub lex_sort {
 1374   PGsort sub {$_[0] cmp $_[1]}, @_;
 1375 }
 1376 sub num_sort {
 1377   PGsort sub {$_[0] <=> $_[1]}, @_;
 1378 }
 1379 
 1380 
 1381 =head2 Macros for handling tables
 1382 
 1383   Usage:
 1384   begintable( number_of_columns_in_table)
 1385   row(@dataelements)
 1386   endtable()
 1387 
 1388 Example of useage:
 1389 
 1390   BEGIN_TEXT
 1391     This problem tests calculating new functions from old ones:$BR
 1392     From the table below calculate the quantities asked for:$BR
 1393     \{begintable(scalar(@firstrow)+1)\}
 1394     \{row(" \(x\) ",@firstrow)\}
 1395     \{row(" \(f(x)\) ", @secondrow)\}
 1396     \{row(" \(g(x)\) ", @thirdrow)\}
 1397     \{row(" \(f'(x)\) ", @fourthrow)\}
 1398     \{row(" \(g'(x)\) ", @fifthrow)\}
 1399     \{endtable()\}
 1400 
 1401    (The arrays contain numbers which are placed in the table.)
 1402 
 1403   END_TEXT
 1404 
 1405 =cut
 1406 
 1407 sub begintable {
 1408   my ($number)=shift;   #number of columns in table
 1409   my %options = @_;
 1410   warn "begintable(cols) requires a number indicating the number of columns" unless defined($number);
 1411   my $out = "";
 1412   if ($displayMode eq 'TeX') {
 1413     $out .= "\n\\par\\smallskip\\begin{center}\\begin{tabular}{"  .  "|c" x $number .  "|} \\hline\n";
 1414     }
 1415   elsif ($displayMode eq 'Latex2HTML') {
 1416     $out .= "\n\\begin{rawhtml} <TABLE , BORDER=1>\n\\end{rawhtml}";
 1417     }
 1418   elsif ($displayMode eq 'HTML' || $displayMode eq 'HTML_tth') {
 1419     $out .= "<TABLE BORDER=1>\n"
 1420   }
 1421   else {
 1422     $out = "Error: PGchoicemacros: begintable: Unknown displayMode: $displayMode.\n";
 1423     }
 1424   $out;
 1425   }
 1426 
 1427 sub endtable {
 1428   my $out = "";
 1429   if ($displayMode eq 'TeX') {
 1430     $out .= "\n\\end {tabular}\\end{center}\\par\\smallskip\n";
 1431     }
 1432   elsif ($displayMode eq 'Latex2HTML') {
 1433     $out .= "\n\\begin{rawhtml} </TABLE >\n\\end{rawhtml}";
 1434     }
 1435   elsif ($displayMode eq 'HTML' || $displayMode eq 'HTML_tth') {
 1436     $out .= "</TABLE>\n";
 1437     }
 1438   else {
 1439     $out = "Error: PGchoicemacros: endtable: Unknown displayMode: $displayMode.\n";
 1440     }
 1441   $out;
 1442   }
 1443 
 1444 
 1445 sub row {
 1446   my @elements = @_;
 1447   my $out = "";
 1448   if ($displayMode eq 'TeX') {
 1449     while (@elements) {
 1450       $out .= shift(@elements) . " &";
 1451       }
 1452      chop($out); # remove last &
 1453      $out .= "\\\\ \\hline \n";
 1454      # carriage returns must be added manually for tex
 1455     }
 1456   elsif ($displayMode eq 'Latex2HTML') {
 1457     $out .= "\n\\begin{rawhtml}\n<TR>\n\\end{rawhtml}\n";
 1458     while (@elements) {
 1459       $out .= " \n\\begin{rawhtml}\n<TD> \n\\end{rawhtml}\n" . shift(@elements) . " \n\\begin{rawhtml}\n</TD> \n\\end{rawhtml}\n";
 1460       }
 1461     $out .= " \n\\begin{rawhtml}\n</TR> \n\\end{rawhtml}\n";
 1462   }
 1463   elsif ($main::displayMode eq 'HTML' || $main::displayMode eq 'HTML_tth') {
 1464     $out .= "<TR>\n";
 1465     while (@elements) {
 1466       $out .= "<TD>" . shift(@elements) . "</TD>";
 1467       }
 1468     $out .= "\n</TR>\n";
 1469   }
 1470   else {
 1471     $out = "Error: PGchoicemacros: row: Unknown displayMode: $main::displayMode.\n";
 1472     }
 1473   $out;
 1474 }
 1475 
 1476 =head2 Macros for displaying static images
 1477 
 1478   Usage:
 1479   $string = image($image, width => 100, height => 100, tex_size => 800)
 1480   $string = image([$image1, $image2], width => 100, height => 100, tex_size => 800)
 1481   $string = caption($string);
 1482   $string = imageRow([$image1, $image2 ], [$caption1, $caption2]);
 1483            # produces a complete table with rows of pictures.
 1484 
 1485 
 1486 =cut
 1487 
 1488 #   More advanced macros
 1489 sub image {
 1490   my $image_ref  = shift;
 1491     my @opt = @_;
 1492     unless (scalar(@opt) % 2 == 0 ) {
 1493       warn "ERROR in image macro.  A list of macros must be inclosed in square brackets.";
 1494     }
 1495     my %in_options = @opt;
 1496     my %known_options = ( width   => 100,
 1497                           height  => 100,
 1498                           tex_size  => 800
 1499                          );
 1500 #   # handle options
 1501     my %out_options = %known_options;
 1502   foreach my $opt_name (keys %in_options) {
 1503     if ( exists( $known_options{$opt_name} ) ) {
 1504       $out_options{$opt_name} = $in_options{$opt_name} if exists( $in_options{$opt_name} ) ;
 1505     } else {
 1506       die "Option $opt_name not defined for image. " .
 1507       "Default options are:<BR> ", display_options2(%known_options);
 1508 
 1509     }
 1510   }
 1511   my $width     = $out_options{width};
 1512   my $height    = $out_options{height};
 1513   my $tex_size  = $out_options{tex_size};
 1514   my $width_ratio = $tex_size*(.001);
 1515   my @image_list = ();
 1516 
 1517   if (ref($image_ref) =~ /ARRAY/ ) {
 1518     @image_list = @{$image_ref};
 1519   } else {
 1520     push(@image_list,$image_ref);
 1521   }
 1522   my @output_list = ();
 1523     while(@image_list) {
 1524 
 1525     my $imageURL = alias(shift @image_list);
 1526     my $out="";
 1527 
 1528 
 1529     if ($main::displayMode eq 'TeX') {
 1530       $out = qq!\\includegraphics[width=$width_ratio\\linewidth]{$imageURL}\n !
 1531       }
 1532     elsif ($main::displayMode eq 'Latex2HTML') {
 1533       $out = qq!\\begin{rawhtml}\n<A HREF= "$imageURL" TARGET="ZOOM"><IMG SRC="$imageURL"  WIDTH="$width" HEIGHT="$height"></A>\n
 1534       \\end{rawhtml}\n !
 1535       }
 1536     elsif ($main::displayMode eq 'HTML' || $main::displayMode eq 'HTML_tth') {
 1537       $out = qq!<A HREF= "$imageURL" TARGET="ZOOM"><IMG SRC="$imageURL"  WIDTH="$width" HEIGHT="$height"></A>
 1538       !
 1539       }
 1540     else {
 1541       $out = "Error: PGchoicemacros: image: Unknown displayMode: $main::displayMode.\n";
 1542       }
 1543     push(@output_list, $out);
 1544   }
 1545   wantarray ? @output_list : $output_list[0] ;
 1546 }
 1547 
 1548 # This is legacy code.
 1549 sub images {
 1550   my @in = @_;
 1551   my @outlist = ();
 1552   while (@in) {
 1553      push(@outlist,&image( shift(@in) ) );
 1554    }
 1555   @outlist;
 1556 }
 1557 
 1558 
 1559 sub caption {
 1560   my ($out) = @_;
 1561   $out = " $out \n" if $main::displayMode eq 'TeX';
 1562   $out = " $out  " if $main::displayMode eq 'HTML';
 1563   $out = " $out  " if $main::displayMode eq 'HTML_tth';
 1564   $out = " $out  " if $main::displayMode eq 'Latex2HTML';
 1565     $out;
 1566 }
 1567 
 1568 sub captions {
 1569   my @in = @_;
 1570   my @outlist = ();
 1571   while (@in) {
 1572      push(@outlist,&caption( shift(@in) ) );
 1573   }
 1574   @outlist;
 1575 }
 1576 
 1577 sub imageRow {
 1578 
 1579   my $pImages = shift;
 1580   my $pCaptions=shift;
 1581   my $out = "";
 1582   my @images = @$pImages;
 1583   my @captions = @$pCaptions;
 1584   my $number = @images;
 1585   # standard options
 1586   my %options = ( 'tex_size' => 200,  # width for fitting 4 across
 1587                   'height' => 100,
 1588                   'width' => 100,
 1589                   @_            # overwrite any default options
 1590                 );
 1591 
 1592   if ($main::displayMode eq 'TeX') {
 1593     $out .= "\n\\par\\smallskip\\begin{center}\\begin{tabular}{"  .  "|c" x $number .  "|} \\hline\n";
 1594     while (@images) {
 1595       $out .= &image( shift(@images),%options ) . '&';
 1596     }
 1597     chop($out);
 1598     $out .= "\\\\ \\hline \n";
 1599     while (@captions) {
 1600       $out .= &caption( shift(@captions) ) . '&';
 1601     }
 1602     chop($out);
 1603     $out .= "\\\\ \\hline \n\\end {tabular}\\end{center}\\par\\smallskip\n";
 1604   } elsif ($main::displayMode eq 'Latex2HTML'){
 1605 
 1606     $out .= "\n\\begin{rawhtml} <TABLE  BORDER=1><TR>\n\\end{rawhtml}\n";
 1607     while (@images) {
 1608       $out .= "\n\\begin{rawhtml} <TD>\n\\end{rawhtml}\n" . &image( shift(@images),%options )
 1609               . "\n\\begin{rawhtml} </TD>\n\\end{rawhtml}\n" ;
 1610     }
 1611 
 1612     $out .= "\n\\begin{rawhtml}</TR><TR>\\end{rawhtml}\n";
 1613     while (@captions) {
 1614       $out .= "\n\\begin{rawhtml} <TH>\n\\end{rawhtml}\n".&caption( shift(@captions) )
 1615               . "\n\\begin{rawhtml} </TH>\n\\end{rawhtml}\n" ;
 1616     }
 1617 
 1618     $out .= "\n\\begin{rawhtml} </TR> </TABLE >\n\\end{rawhtml}";
 1619   } elsif ($main::displayMode eq 'HTML' || $main::displayMode eq 'HTML_tth'){
 1620     $out .= "<P>\n <TABLE BORDER=2 CELLPADDING=3 CELLSPACING=2 ><TR ALIGN=CENTER    VALIGN=MIDDLE>\n";
 1621     while (@images) {
 1622       $out .= " \n<TD>". &image( shift(@images),%options ) ."</TD>";
 1623     }
 1624     $out .= "</TR>\n<TR>";
 1625     while (@captions) {
 1626       $out .= " <TH>". &caption( shift(@captions) ) ."</TH>";
 1627     }
 1628     $out .= "\n</TR></TABLE></P>\n"
 1629   }
 1630   else {
 1631     $out = "Error: PGchoicemacros: imageRow: Unknown languageMode: $main::displayMode.\n";
 1632     warn $out;
 1633   }
 1634   $out;
 1635 }
 1636 
 1637 
 1638 ###########
 1639 # Auxiliary macros
 1640 
 1641 sub display_options2{
 1642   my %options = @_;
 1643   my $out_string = "";
 1644   foreach my $key (keys %options) {
 1645     $out_string .= " $key => $options{$key},<BR>";
 1646   }
 1647   $out_string;
 1648 }
 1649 
 1650 
 1651 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9