Parent Directory
|
Revision Log
This is a fairly stable version of Hardcopy. It reports part of the TeX log errors along with whater pdf was produced. This partially satisfies the requests in bug #82 --Mike
1 ################################################################################ 2 # WeBWorK mod_perl (c) 2000-2002 WeBWorK Project 3 # $Id$ 4 ################################################################################ 5 6 package WeBWorK::ContentGenerator::Hardcopy; 7 use base qw(WeBWorK::ContentGenerator); 8 9 =head1 NAME 10 11 WeBWorK::ContentGenerator::Hardcopy - generate a PDF version of one or more 12 problem sets. 13 14 =cut 15 16 use strict; 17 use warnings; 18 use CGI qw(); 19 use File::Path qw(rmtree); 20 use WeBWorK::Form; 21 use WeBWorK::Utils qw(readFile makeTempDirectory); 22 23 sub go { 24 my ($self, $singleSet) = @_; 25 26 my $r = $self->{r}; 27 my $ce = $self->{ce}; 28 my $db = $self->{db}; 29 my @sets = $r->param("hcSet"); 30 my @users = $r->param("hcUser"); 31 32 # add singleSet to the list of sets 33 if (length $singleSet > 0) { 34 $singleSet =~ s/^set//; 35 unshift @sets, $singleSet unless grep { $_ eq $singleSet } @sets; 36 } 37 38 # default user is the effectiveUser 39 unless (@users) { 40 unshift @users, $r->param("effectiveUser"); 41 } 42 43 $self->{user} = $db->getUser($r->param("user")); 44 $self->{permissionLevel} = $db->getPermissionLevel($r->param("user"))->permission(); 45 $self->{effectiveUser} = $db->getUser($r->param("effectiveUser")); 46 $self->{sets} = \@sets; 47 $self->{users} = \@users; 48 $self->{errors} = []; 49 $self->{warnings} = []; 50 51 # security checks 52 my $multiSet = $self->{permissionLevel} > 0; 53 my $multiUser = $self->{permissionLevel} > 0; 54 if (@sets > 1 and not $multiSet) { 55 $self->{generationError} = ["SIMPLE", "You are not permitted to generate hardcopy for multiple sets. Please select a single set and try again."]; 56 } 57 if (@users > 1 and not $multiUser) { 58 $self->{generationError} = ["SIMPLE", "You are not permitted to generate hardcopy for multiple users. Please select a single user and try again."]; 59 } 60 if ($users[0] ne $self->{effectiveUser}->user_id and not $multiUser) { 61 $self->{generationError} = ["SIMPLE", "You are not permitted to generate hardcopy for other users."]; 62 } 63 64 unless ($self->{generationError}) { 65 if ($r->param("generateHardcopy")) { 66 my ($tempDir, $fileName,$errors) = eval { $self->generateHardcopy() }; 67 warn "tempDir $tempDir fileName $fileName "; 68 if ($@) { 69 $self->{generationError} = $@; 70 # In this case no correct pdf file was generated. 71 # there is not much more that can be done 72 # throw the error up higher. 73 # The error is reported in body. 74 # the tempDir was removed in generateHardcopy 75 76 77 } else { 78 my $filePath = "$tempDir/$fileName"; 79 # FIXME this is taking up server time 80 # why not move the file to the tempDir and let the browser pick it up on redirect? 81 # my $hardcopyFilePath = $self->{hardcopyFilePath}; 82 # my $hardcopyFileURL = $self->{hardcopyFileURL}; 83 if ($errors eq '') { 84 $r->content_type("application/x-pdf"); 85 # as per RFC2183: 86 $r->header_out("Content-Disposition", "attachment; filename=$fileName"); 87 $r->send_http_header(); 88 89 local *INPUTFILE; 90 open INPUTFILE, "<", $filePath 91 or die "Failed to read $filePath: $!"; 92 my $buf; 93 while (read INPUTFILE, $buf, 16384) { 94 print $buf; 95 } 96 close INPUTFILE; 97 98 rmtree($tempDir); 99 } else { 100 101 } 102 103 return; 104 } 105 } 106 } 107 108 $r->content_type("text/html"); 109 $r->send_http_header(); 110 $self->template($ce->{templates}->{system}, $singleSet); 111 } 112 113 # ----- 114 115 sub path { 116 my ($self, undef, $args) = @_; 117 118 my $ce = $self->{ce}; 119 my $root = $ce->{webworkURLs}->{root}; 120 my $courseName = $ce->{courseName}; 121 return $self->pathMacro($args, 122 "Home" => "$root", 123 $courseName => "$root/$courseName", 124 "Hardcopy Generator" => "", 125 ); 126 } 127 128 sub title { 129 return "Hardcopy Generator"; 130 } 131 132 sub body { 133 my $self = shift; 134 135 if ($self->{generationError}) { 136 if (ref $self->{generationError} eq "ARRAY") { 137 my ($disposition, @rest) = @{$self->{generationError}}; 138 if ($disposition eq "PGFAIL") { 139 $self->multiErrorOutput(@{$self->{errors}}); 140 return ""; 141 } elsif ($disposition eq "FAIL") { 142 print $self->errorOutput(@rest); 143 return ""; 144 } elsif ($disposition eq "RETRY") { 145 print $self->errorOutput(@rest); 146 } else { # a "simple" error 147 print CGI::p(CGI::font({-color=>"red"}, @rest)); 148 } 149 } else { 150 # not something we were expecting... 151 die $self->{generationError}; 152 } 153 } 154 if (@{$self->{warnings}}) { 155 # FIXME: this code will only be reached if there was also a 156 # generation error, because otherwise the module will send 157 # the PDF instead. DAMN! 158 $self->multiWarningOutput(@{$self->{warnings}}); 159 } 160 $self->displayForm(); 161 } 162 163 sub multiErrorOutput($@) { 164 my ($self, @errors) = @_; 165 166 print CGI::h2("Software Errors"); 167 print CGI::p(<<EOF); 168 WeBWorK has encountered one or more software errors while attempting to process 169 these problem sets. It is likely that there are errors in the problems 170 themselves. If you are a student, contact your professor to have the errors 171 corrected. If you are a professor, please consut the error output below for 172 more informaiton. 173 EOF 174 foreach my $error (@errors) { 175 print CGI::h3("Set: ", $error->{set}, ", Problem: ", $error->{problem}); 176 print CGI::h4("Error messages"), CGI::blockquote(CGI::pre($error->{message})); 177 print CGI::h4("Error context"), CGI::blockquote(CGI::pre($error->{context})); 178 } 179 } 180 181 sub multiWarningOutput($@) { 182 my ($self, @warnings) = @_; 183 184 print CGI::h2("Software Warnings"); 185 print CGI::p(<<EOF); 186 WeBWorK has encountered one or more warnings while attempting to process these 187 problem sets. It is likely that this indicates errors or ambiguitiees in the 188 problems themselves. If you are a student, contact your professor to have the 189 problems corrected. If you are a professor, please consut the warning output 190 below for more informaiton. 191 EOF 192 foreach my $warning (@warnings) { 193 print CGI::h3("Set: ", $warning->{set}, ", Problem: ", $warning->{problem}); 194 print CGI::h4("Error messages"), CGI::blockquote(CGI::pre($warning->{message})); 195 } 196 } 197 198 # ----- 199 200 sub displayForm($) { 201 my $self = shift; 202 my $r = $self->{r}; 203 my $db = $self->{db}; 204 205 print CGI::start_p(), "Select the problem sets for which to generate hardcopy versions."; 206 if ($self->{permissionLevel} > 0) { 207 print "You may also select multiple users from the users list. You will receive hardcopy for each (set, user) pair."; 208 } 209 print CGI::end_p(); 210 211 print CGI::start_form(-method=>"POST", -action=>$r->uri); 212 print $self->hidden_authen_fields(); 213 print CGI::h3("Options"); 214 print CGI::p("You may choose to show any of the following data. Correct answers and solutions are only available to privileged users or after the answer date of the problem set."); 215 print CGI::p( 216 CGI::checkbox( 217 -name => "showCorrectAnswers", 218 -checked => $r->param("showCorrectAnswers") || 0, 219 -label => "Correct answers", 220 ), CGI::br(), 221 CGI::checkbox( 222 -name => "showHints", 223 -checked => $r->param("showHints") || 0, 224 -label => "Hints", 225 ), CGI::br(), 226 CGI::checkbox( 227 -name => "showSolutions", 228 -checked => $r->param("showSolutions") || 0, 229 -label => "Solutions", 230 ), 231 ); 232 print CGI::start_table({-width=>"100%"}), CGI::start_Tr({-valign=>"top"}); 233 234 my $multiSet = $self->{permissionLevel} > 0; 235 my $multiUser = $self->{permissionLevel} > 0; 236 my $preOpenSets = $self->{permissionLevel} > 0; 237 238 # set selection menu 239 { 240 print CGI::start_td(); 241 print CGI::h3("Sets"); 242 print CGI::start_table(); 243 my @sets; 244 push @sets, $db->getMergedSet($self->{effectiveUser}->user_id, $_) 245 foreach ($db->listUserSets($self->{effectiveUser}->user_id)); 246 @sets = sort { $a->set_id cmp $b->set_id } @sets; 247 foreach my $set (@sets) { 248 my $checked = grep { $_ eq $set->set_id } @{$self->{sets}}; 249 my $control; 250 if (time < $set->open_date and not $preOpenSets) { 251 $control = ""; 252 } else { 253 if ($multiSet) { 254 $control = CGI::checkbox( 255 -name=>"hcSet", 256 -value=>$set->set_id, 257 -label=>"", 258 -checked=>$checked 259 ); 260 } else { 261 $control = CGI::radio_group( 262 -name=>"hcSet", 263 -values=>[$set->set_id], 264 -default=>($checked ? $set->set_id : "-"), 265 -labels=>{$set->set_id => ""} 266 ); 267 } 268 } 269 print CGI::Tr(CGI::td([ 270 $control, 271 $set->set_id, 272 ])); 273 } 274 print CGI::end_table(); 275 print CGI::end_td(); 276 } 277 278 # user selection menu 279 if ($multiUser) { 280 print CGI::start_td(); 281 print CGI::h3("Users"); 282 print CGI::start_table(); 283 #print CGI::Tr( 284 # CGI::td(CGI::checkbox(-name=>"hcAllUsers", -value=>"1", -label=>"")), 285 # CGI::td({-colspan=>"2"}, "All Users"), 286 #); 287 #print CGI::Tr(CGI::td({-colspan=>"3"}, " ")); 288 my @users; 289 push @users, $self->{db}->getUser($_) 290 foreach ($self->{db}->listUsers()); 291 @users = sort { $a->last_name cmp $b->last_name } @users; 292 foreach my $user (@users) { 293 my $checked = grep { $_ eq $user->user_id } @{$self->{users}}; 294 print CGI::Tr(CGI::td([ 295 CGI::checkbox(-name=>"hcUser", -value=>$user->user_id, -label=>"", -checked=>$checked), 296 $user->user_id, 297 $user->last_name.", ".$user->first_name, 298 ])); 299 } 300 print CGI::end_table(); 301 print CGI::end_td(); 302 } 303 304 print CGI::end_Tr(), CGI::end_table(); 305 print CGI::p({-align=>"center"}, 306 CGI::submit(-name=>"generateHardcopy", -label=>"Generate Hardcopy")); 307 print CGI::end_form(); 308 309 return ""; 310 } 311 312 sub generateHardcopy($) { 313 my $self = shift; 314 my $ce = $self->{ce}; 315 my @sets = @{$self->{sets}}; 316 my @users = @{$self->{users}}; 317 my $multiSet = $self->{permissionLevel} > 0; 318 my $multiUser = $self->{permissionLevel} > 0; 319 # sanity checks 320 unless (@sets) { 321 die ["RETRY", "No sets were specified."]; 322 } 323 unless (@users) { 324 die ["RETRY", "No users were specified."]; 325 } 326 327 # determine where hardcopy is going to go 328 my $tempDir = makeTempDirectory($ce->{webworkDirs}->{tmp}, "webwork-hardcopy"); 329 330 # determine name of PDF file #FIXME it might be best to have the effective user in here somewhere 331 my $courseName = $self->{ce}->{courseName}; 332 my $fileNameSet = (@sets > 1 ? "multiset" : $sets[0]); 333 my $fileNameUser = (@users > 1 ? "multiuser" : $users[0]); 334 my $fileName = "$courseName.$fileNameUser.$fileNameSet.pdf"; 335 336 # for each user ... generate TeX for each set 337 my $tex; 338 # 339 # the document tex preamble 340 $tex .= $self->texInclude($self->{ce}->{webworkFiles}->{hardcopySnippets}->{preamble}); 341 # separate users by page break, or something 342 foreach my $user (@users) { 343 $tex .= $self->getMultiSetTeX($user, @sets); 344 if (@users) { 345 # separate users, but not after the last set 346 $tex .= $self->texInclude($self->{ce}->{webworkFiles}->{hardcopySnippets}->{userDivider}); 347 } 348 349 } 350 # the document postamble 351 $tex .= $self->texInclude($self->{ce}->{webworkFiles}->{hardcopySnippets}->{postamble}); 352 353 # deal with PG errors 354 if (@{$self->{errors}}) { 355 die ["PGFAIL"]; 356 } 357 358 # FIXME: add something like: 359 #if (@{$self->{warnings}}) { 360 # $self->{generationWarnings} = 1; 361 #} 362 # ??????? 363 364 # "try" to generate pdf 365 my $errors = ''; 366 eval { $self->latex2pdf($tex, $tempDir, $fileName) }; 367 if ($@) { 368 $errors = $@; 369 #$errors =~ s/\n/<br>/g; # make this readable on HTML FIXME make this a Utils. filter (Error2HTML) 370 # clean up temp directory 371 rmtree($tempDir); 372 die ["FAIL", "Failed to generate PDF from tex", $errors]; #throw error to subroutine body 373 } 374 375 return $tempDir, $fileName; 376 } 377 378 # ----- 379 380 sub latex2pdf { 381 # this is a little ad-hoc function which I will replace with a LaTeX 382 # module at some point (or put it in Utils). 383 my ($self, $tex, $tempDir, $fileName) = @_; 384 my $finalFile = "$tempDir/$fileName"; 385 my $ce = $self->{ce}; 386 387 # Location for hardcopy file to be downloaded 388 # FIXME this should use surePathToTmpFile 389 my $hardcopyTempDirectory = $ce->{courseDirs}->{html_temp}."/hardcopy"; 390 mkdir ($hardcopyTempDirectory) or die "Unable to make $hardcopyTempDirectory" unless -e $hardcopyTempDirectory; 391 my $hardcopyFilePath = "$hardcopyTempDirectory/$fileName"; 392 my $hardcopyFileURL = $ce->{courseURLs}->{html_temp}."/hardcopy/$fileName"; 393 $self->{hardcopyFilePath} = $hardcopyFilePath; 394 $self->{hardcopyFileURL} = $hardcopyFileURL; 395 ## create a temporary directory for tex to shit in 396 #my $wd = tempdir("webwork-hardcopy-XXXXXXXX", TMPDIR => 1); 397 # - we're using the existing temp dir. now 398 399 my $wd = $tempDir; 400 my $texFile = "$wd/hardcopy.tex"; 401 my $pdfFile = "$wd/hardcopy.pdf"; 402 my $logFile = "$wd/hardcopy.log"; 403 404 # write the tex file 405 local *TEX; 406 open TEX, ">", $texFile or die "Failed to open $texFile: $!\n".CGI::br(); 407 print TEX $tex; 408 close TEX; 409 410 # call pdflatex - we don't want to chdir in the mod_perl process, as 411 # that might step on the feet of other things (esp. in Apache 2.0) 412 my $pdflatex = $ce->{externalPrograms}->{pdflatex}; 413 my $pdflatexResult = system "cd $wd && $pdflatex $texFile"; 414 415 # Even with errors there may be a valid pdfFile. Move it to where we can get it. 416 if (-e $pdfFile) { 417 # move resulting PDF file to appropriate location 418 # FIXME don't fix everything at once :-) 419 system "/bin/mv", $pdfFile, $finalFile 420 and die "Failed to mv: $pdfFile to $finalFile<br>\n Quite likely this means that there ". 421 "is not sufficient write permission for some directory.<br>$!<br>\n"; 422 # moving to course tmp/hardcopy directory 423 # system "/bin/mv", $pdfFile, $hardcopyFilePath 424 # and die "Failed to mv: $pdfFile to $hardcopyFilePath<br> Quite likely this means that there ". 425 # "is not sufficient write permission for some directory.<br>$!\n".CGI::br(); 426 } 427 # Alert the world that the tex file did not process perfectly. 428 if ($pdflatexResult) { 429 # something bad happened 430 my $textErrorMessage = "Call to $pdflatex failed: $!\n".CGI::br(); 431 432 # move what output there is to someplace where it can be read 433 system "/bin/mv", $finalFile, $hardcopyFilePath 434 and die "Failed to mv: $pdfFile to $hardcopyFilePath<br> Quite likely this means that there ". 435 "is not sufficient write permission for some directory.<br>$!\n".CGI::br(); 436 if (-e $hardcopyFilePath ) { 437 # FIXME Misuse of html tags!!! 438 $textErrorMessage.= "<h4>Some pdf output was produced and is available ". CGI::a({-href=>$hardcopyFileURL},"here.</h4>").CGI::hr(); 439 } 440 # report logfile 441 if (-e $logFile) { 442 $textErrorMessage .= "pdflatex ran, but did not succeed. This suggests an error in the TeX\n".CGI::br(); 443 $textErrorMessage .= "version of one of the problems, or a problem with the pdflatex system.\n".CGI::br(); 444 my $logFileContents = eval { readTexErrorLog($logFile) }; 445 if ($@) { 446 $textErrorMessage .= "Additionally, the pdflatex log file could not be read, though it exists.\n".CGI::br(); 447 } else { 448 $textErrorMessage .= "The essential contents of the TeX log are as follows:\n".CGI::hr().CGI::br(); 449 $textErrorMessage .= "$logFileContents\n".CGI::br().CGI::br(); 450 } 451 } else { 452 $textErrorMessage .= "No log file was created, suggesting that pdflatex never ran. Check the WeBWorK\n".CGI::br(); 453 $textErrorMessage .= "configuration to ensure that the path to pdflatex is correct.\n".CGI::br(); 454 } 455 die $textErrorMessage; 456 } 457 458 459 460 ## remove temporary directory 461 ##FIXME rmtree is commented out only for debugging purposes. 462 ##print STDERR "tex temp directory at $wd"; 463 #rmtree($wd, 0, 0); 464 # - not creating the temp dir here anymore 465 466 -e $finalFile or die "Failed to create $finalFile for no apparent reason.\n"; 467 } 468 469 # ----- 470 # FIXME move to Utils? probably not 471 sub readTexErrorLog { 472 my $filePath = shift; 473 my $print_error_switch = 0; 474 my $line=''; 475 my @message=(); 476 #local($/ ) = "\n"; 477 open(LOGFILE,"<$filePath") or die "Can't read $filePath"; 478 while (<LOGFILE>) { 479 $line = $_; 480 $print_error_switch = 1 if $line =~ /^!/; # after a fatal error start printing messages 481 push(@message, protect_HTML($line)) if $print_error_switch; 482 } 483 close($filePath); 484 join("<br>\n",@message); 485 } 486 sub protect_HTML { 487 my $line = shift; 488 chomp($line); 489 $line =~s/\&/&/g; 490 $line =~s/</</g; 491 $line =~s/>/>/g; 492 $line; 493 } 494 sub texBlockComment(@) { return "\n".("%"x80)."\n%% ".join("", @_)."\n".("%"x80)."\n\n"; } 495 496 sub getMultiSetTeX { 497 my ($self, $effectiveUserName,@sets) = @_; 498 my $ce = $self->{ce}; 499 my $tex = ""; 500 501 502 503 while (defined (my $setName = shift @sets)) { 504 $tex .= $self->getSetTeX($effectiveUserName, $setName); 505 if (@sets) { 506 # divide sets, but not after the last set 507 $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{setDivider}); 508 } 509 } 510 511 512 513 return $tex; 514 } 515 516 sub getSetTeX { 517 my ($self, $effectiveUserName,$setName) = @_; 518 my $ce = $self->{ce}; 519 my $db = $self->{db}; 520 521 # FIXME (debug code line next) 522 # print STDERR "Creating set $setName for $effectiveUserName \n"; 523 524 # FIXME We could define a default for the effective user if no correct name is passed in. 525 # I'm not sure that it is wise. 526 my $effectiveUser = $db->getUser($effectiveUserName); 527 528 my @problemNumbers = sort { $a <=> $b } 529 $db->listUserProblems($effectiveUserName, $setName); 530 531 # get header and footer 532 my $setHeader = $db->getMergedSet($effectiveUserName, $setName)->set_header 533 || $ce->{webworkFiles}->{hardcopySnippets}->{setHeader}; 534 # database doesn't support the following yet :( 535 #my $setFooter = $wwdb->getMergedSet($effectiveUserName, $setName)->set_footer 536 # || $ce->{webworkFiles}->{hardcopySnippets}->{setFooter}; 537 # so we don't allow per-set customization, which is probably okay :) 538 my $setFooter = $ce->{webworkFiles}->{hardcopySnippets}->{setFooter}; 539 540 my $tex = ""; 541 542 # render header 543 $tex .= texBlockComment("BEGIN $setName : $setHeader"); 544 $tex .= $self->getProblemTeX($effectiveUser,$setName, 0, $setHeader); 545 546 # render each problem 547 while (my $problemNumber = shift @problemNumbers) { 548 $tex .= texBlockComment("BEGIN $setName : $problemNumber"); 549 $tex .= $self->getProblemTeX($effectiveUser,$setName, $problemNumber); 550 if (@problemNumbers) { 551 # divide problems, but not after the last problem 552 $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{problemDivider}); 553 } 554 } 555 556 # render footer 557 $tex .= texBlockComment("BEGIN $setName : $setFooter"); 558 $tex .= $self->getProblemTeX($effectiveUser,$setName, 0, $setFooter); 559 560 return $tex; 561 } 562 563 sub getProblemTeX { 564 my ($self, $effectiveUser, $setName, $problemNumber, $pgFile) = @_; 565 my $r = $self->{r}; 566 my $ce = $self->{ce}; 567 my $db = $self->{db}; 568 569 # Should we provide a default user ? I think not FIXME 570 571 # $effectiveUser = $self->{effectiveUser} unless defined($effectiveUser); 572 my $permissionLevel = $self->{permissionLevel}; 573 my $set = $db->getMergedSet($effectiveUser->user_id, $setName); 574 my $psvn = $set->psvn(); 575 576 # decide what to do about problem number 577 my $problem; 578 if ($problemNumber) { 579 $problem = $db->getMergedProblem($effectiveUser->user_id, $setName, $problemNumber); 580 } elsif ($pgFile) { 581 $problem = WeBWorK::DB::Record::UserProblem->new( 582 set_id => $set->set_id, 583 problem_id => 0, 584 login_id => $effectiveUser->user_id, 585 source_file => $pgFile, 586 # the rest of Problem's fields are not needed, i think 587 ); 588 } 589 590 # figure out if we're allowed to get solutions and call PG->new accordingly. 591 my $showCorrectAnswers = $r->param("showCorrectAnswers") || 0; 592 my $showHints = $r->param("showHints") || 0; 593 my $showSolutions = $r->param("showSolutions") || 0; 594 unless ($permissionLevel > 0 or time > $set->answer_date) { 595 $showCorrectAnswers = 0; 596 $showSolutions = 0; 597 } 598 599 my $pg = WeBWorK::PG->new( 600 $ce, 601 $effectiveUser, 602 $r->param('key'), 603 $set, 604 $problem, 605 $psvn, 606 {}, # no form fields! 607 { # translation options 608 displayMode => "tex", 609 showHints => $showHints, 610 showSolutions => $showSolutions, 611 processAnswers => $showCorrectAnswers, 612 }, 613 ); 614 615 if ($pg->{warnings} ne "") { 616 push @{$self->{warnings}}, { 617 set => $setName, 618 problem => $problemNumber, 619 message => $pg->{warnings}, 620 }; 621 } 622 623 if ($pg->{flags}->{error_flag}) { 624 push @{$self->{errors}}, { 625 set => $setName, 626 problem => $problemNumber, 627 message => $pg->{errors}, 628 context => $pg->{body_text}, 629 }; 630 # if there was an error, body_text contains 631 # the error context, not TeX code 632 $pg->{body_text} = undef; 633 } else { 634 # append list of correct answers to body text 635 if ($showCorrectAnswers && $problemNumber != 0) { 636 my $correctTeX = "Correct Answers:\\par\\begin{itemize}\n"; 637 foreach my $ansName (@{$pg->{flags}->{ANSWER_ENTRY_ORDER}}) { 638 my $correctAnswer = $pg->{answers}->{$ansName}->{correct_ans}; 639 $correctAnswer =~ s/\^/\\\^\{\}/g; 640 $correctAnswer =~ s/\_/\\\_/g; 641 $correctTeX .= "\\item $correctAnswer\n"; 642 } 643 $correctTeX .= "\\end{itemize} \\par\n"; 644 $pg->{body_text} .= $correctTeX; 645 } 646 } 647 return $pg->{body_text}; 648 } 649 650 sub texInclude { 651 my ($self, $texFile) = @_; 652 my $tex = ""; 653 654 $tex .= texBlockComment("BEGIN: $texFile"); 655 eval { 656 $tex .= readFile($texFile) 657 }; 658 if ($@) { 659 $tex .= texBlockComment($@); 660 } 661 662 return $tex; 663 } 664 665 1;
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |