Parent Directory
|
Revision Log
initial import
1 #!/usr/bin/perl 2 3 ## $Id$ 4 5 #################################################################### 6 # Copyright @ 1995-1998 University of Rochester 7 # All Rights Reserved 8 #################################################################### 9 10 # ############################################################# 11 # ############################################################# 12 # File: FILE.pl 13 # This contains the subroutines for creating problem files, 14 # recording scores, printing delimited files, etc. 15 # ############################################################# 16 # ############################################################# 17 use strict; 18 19 20 # Variables global to this file 21 22 my $scoringDirectory = getCourseScoringDirectory(); 23 my $templateDirectory = getCourseTemplateDirectory(); 24 my $scriptDirectory = getWebworkScriptDirectory(); 25 my $databaseDirectory = getCourseDatabaseDirectory(); 26 27 my $DELIM = $Global::delim; 28 my $scoreFilePrefix = $Global::scoreFilePrefix; 29 my $scoring_log = $Global::scoring_log; 30 my $dash = $Global::dash; 31 my $DAT = $Global::dat; 32 my @STATUS_DROP = @Global::statusDrop; 33 34 my $dd = getDirDelim(); 35 36 37 38 # Subroutines: 39 40 41 #sub createProblem { 42 # my($mode,$probNum,$psvn,$courseName,$sourceref,$refSubmittedAnswers)=@_; 43 # my @out; 44 # #&attachProbSetRecord($psvn); 45 # my $fileName = &getProblemFileName($probNum,$psvn); 46 # #print "content-type: text/plain\n\ngetProblemFileName gives $fileName\n probNum =$probNum and psvn =$psvn\n"; 47 # $fileName = $main::in{'probFileName'} if defined($main::in{'probFileName'}); 48 # #print "now fileName gives $fileName\n probNum =$probNum and psvn =$psvn\n"; 49 # 50 ####Define global variables for the interpreter and seed random function 51 # #srand(&getProblemSeed($probNum,$psvn)); 52 ## print "\n\nContent-type: text/html\n\nERROR: createProblem: Submitted Answers list |$refSubmittedAnswers| not passed\n\n" 53 ## unless defined($refSubmittedAnswers); 54 # defineProblemVars($mode,$probNum, $psvn,$courseName,$refSubmittedAnswers); 55 # @out = &createLines($mode,$fileName,$sourceref); 56 #} 57 # 58 #sub createProblem2 { 59 # my ($mode,$probNum,$psvn,$courseName,$sourceref,$refSubmittedAnswers)=@_; 60 # my %envir=defineProblemEnvir($mode,$probNum,$psvn,$courseName,$refSubmittedAnswers); 61 # #print %envir; 62 # createPGtext($sourceref,\%envir); 63 #} 64 # 65 66 ######## 67 ## Where is createNumberedInsert used??? 68 ######## 69 # sub createNumberedInsert { 70 # my($mode,$probNum,$psvn)=@_; 71 # my @out; 72 # # &attachProbSetRecord($psvn); 73 # my $fileName = &getInsertFileName($num,$psvn); 74 # 75 # ###Define global variables for the interpreter 76 # defineProblemVars($mode,$probNum, $psvn,$refSubmittedAnswers); 77 # @out = &createLines($mode, $fileName); 78 # } 79 80 #This subroutine has been substituted by createProblem, because it is 81 # virtually identical to it 82 #sub createInsert { 83 # my($mode,$fileName,$psvn,$courseName,$sourceref)=@_; 84 # my @out; 85 # # &attachProbSetRecord($psvn); 86 # 87 # 88 # ###Define global variables for the interpreter 89 # # This is for the probSet.pl page so $probNum is not well defined 90 # my $probNum = 0; 91 # defineProblemVars($mode,$probNum, $psvn,$courseName); 92 # @out = &createLines($mode,$fileName,$sourceref); 93 #} 94 95 #sub defineProblemVars { 96 # my ($mode,$probNum,$psvn,$courseName,$refSubmittedAnswers) = @_; 97 # @main::submittedAnswers = @$refSubmittedAnswers if defined($refSubmittedAnswers); 98 # $main::psvnNumber = $psvn; 99 # $main::psvn = $psvn; 100 # $main::studentName = &getStudentName($psvn); 101 # $main::studentLogin = &getStudentLogin($psvn); 102 # $main::sectionName = &getClassSection($psvn); 103 # $main::sectionNumber = &getClassSection($psvn); 104 # $main::setNumber = &getSetNumber($psvn); 105 # $main::questionNumber = $probNum; 106 # $main::probNum = $probNum; 107 # $main::openDate = &getOpenDate($psvn); 108 # $main::formatedOpenDate = &formatDateAndTime(&getOpenDate($psvn)); 109 # $main::dueDate = &getDueDate($psvn); 110 # $main::formatedDueDate = &formatDateAndTime(&getDueDate($psvn)); 111 # $main::answerDate = &getAnswerDate($psvn); 112 # $main::formatedAnswerDate = &formatDateAndTime(&getAnswerDate($psvn)); 113 # $main::problemValue = &getProblemValue($probNum,$psvn); 114 # $main::fileName = &getProblemFileName($probNum,$psvn); 115 # $main::probFileName = &getProblemFileName($probNum,$psvn); 116 # $main::templateDirectory = &getCourseTemplateDirectory(); 117 # $main::languageMode = $mode; 118 # $main::outputMode = $mode; 119 # $main::courseName = $courseName; 120 # $main::sessionKey = ( defined($main::in{'key'}) ) ?$main::in{'key'} : " "; 121 # #my $seed ; 122 # #if ( defined( $inputs{'seed'}) && $permissions == $Global::instructor_permissions ) { 123 # # $seed = $inputs{'seed'}; 124 # #} else { 125 # # $seed = &getProblemSeed($probNum, $psvn); 126 # #} 127 # #$main::problemSeed = $seed; 128 # ##Move srand to PGeval, after unpacking it 129 # #srand($main::problemSeed); 130 # 131 #} 132 133 134 ###no longer use this subroutine 135 ###createPGtext calls PGeval directly 136 ###the language is figured out in the processProblem.pl 137 #sub createLines { 138 # 139 # my ($mode,$fileName,$sourceref) = @_; 140 # my @out; 141 # 142 # 143 ### Set current directory 144 ## my $pathName = $fileName; 145 ## $pathName =~ s|[^/]*$||; 146 ## my $currentDirectory = ${templateDirectory} . ${pathName}; 147 ## chdir "$currentDirectory"; 148 ## 149 ## if (! open(INPUT, "${templateDirectory}$fileName") ) { 150 ### If the file can not be found and opened output an error message 151 ## push(@out, "createLines: ERROR: Can't open filename ${templateDirectory}$fileName\n"); 152 # } 153 # else { 154 # 155 # 156 ### Determine language 157 # # print "content-type: text/plain\n\n fileName = $fileName\n"; 158 # $fileName =~ /\.([^\.]*)$/; 159 # my $languageType = $1; 160 # #print "languageType=$languageType<BR>\n"; 161 ### Call interpreter 162 # if ($languageType eq 'qz') { 163 # ##Assign INPUT to problem file 164 # require "${scriptDirectory}qz2sub.pl";; 165 # @out = &qz2($mode); 166 # 167 # @out = post_process_qz($mode, \@out); 168 # } elsif ($languageType eq 'pg') { 169 # #$languageMode = $mode; #Define global variables for the interpreter and seed random function 170 # @out =&PGeval($sourceref); 171 # } else { 172 # $out[0] = "ERROR: createLines: Don't understand languages with extension $languageType.<BR>\n"; 173 # } 174 ## } 175 # @out; 176 #} 177 # 178 179 180 #sub post_process_qz { 181 # my ($mode,$refInput_lines) = @_; 182 # my $col = 70; 183 # my $len = 0.07*$col; 184 # my @output_lines = (); 185 # my $ansName = ""; 186 # my $answerValue = ""; 187 # my $ansCount = 0; 188 # my $line; 189 # foreach $line (@$refInput_lines) { 190 # 191 # if ($line =~ /^\[ans/i) { 192 # 193 # $ansCount++; 194 # $ansName = "answer" . "$ansCount"; 195 # $answerValue = param("$ansName") if defined param("$ansName"); 196 # #print "<BR>$ansName<BR>"; 197 # if ($mode eq 'HTML') { 198 # push(@output_lines, qq(<INPUT TYPE="TEXT" NAME=$ansName VALUE="$answerValue" SIZE="$col" MAXLENGTH="800">\n\n<HR>) ); 199 # } elsif ($mode eq 'Latex2HTML') { 200 # push(@output_lines, qq!\\begin{rawhtml}\n<INPUT TYPE=TEXT SIZE=$col NAME=\"answer$ansCount\" VALUE = \"$main::submittedAnswers[$ansCount]\">\n\\end{rawhtml}\n! ); 201 # 202 # } elsif ($mode eq 'TeX') { 203 # push(@output_lines, "\rule{${len}in}{.01in}" ); 204 # } else { 205 # push(@output_lines, "ERROR: post_process_qz: mode=$mode is not recognized"); 206 # } 207 # push(@main::PG_ANSWERS, create_qz_ans_function($line)); 208 # } else { 209 # push(@output_lines, $line); 210 # } 211 # } 212 # 213 # @output_lines; 214 #} 215 #sub create_qz_ans_function{ 216 # my $line = shift @_; 217 # my $answer_evaluator = 0; 218 # my ($format, $correctAnswer) = split("=",$line); 219 # 220 # if ($format =~ /^\[ans:([0-9]*),?([0-9\.\-\+eE]*)%?/ ) { #numeric compare--the guts of std_num_cmp 221 # my $accuracy = $2; 222 # my $precision=$1; 223 # my $relpercentTol = $2; 224 # $relpercentTol = .01 unless($relpercentTol); 225 # my $tol = .01*$relpercentTol; 226 # my $formattedCorrectAnswer = sprintf("%10.${precision}g",$correctAnswer ); 227 # $answer_evaluator = sub { 228 # my $in = shift @_; 229 # my $formattedSubmittedAnswer = ""; 230 # my $PGanswerMessage = ""; 231 # my ($inVal,$correctVal); 232 # $correctVal = eval($correctAnswer); 233 # $@=''; 234 # $inVal = eval($in); 235 # if ($@) { ##error message from eval 236 # $formattedSubmittedAnswer = $@; 237 # $formattedSubmittedAnswer =~ s/at.*line [\d]*//g; 238 # $formattedSubmittedAnswer =~ s/called//g; 239 # $formattedSubmittedAnswer =~ s/&main:://g; 240 # $formattedSubmittedAnswer =~ s/chunk [\d]*//g; 241 # } else { 242 # $formattedSubmittedAnswer = sprintf($format,$inVal); 243 # } 244 # 245 # if ($correctVal == 0) { 246 # $tol = 1E-12; ## want $tol to be non zero 247 # } else { 248 # $tol = abs($tol*$correctVal); 249 # } 250 # my $correctQ =0; 251 # $correctQ = 1 if ((not $@) and 252 # (abs( $inVal - $correctVal ) <= $tol)); 253 # if ($@) { 254 # $PGanswerMessage = "There is a syntax error in your answer"; 255 # } 256 # ($correctQ,$formattedCorrectAnswer,$formattedSubmittedAnswer,$PGanswerMessage); 257 # }; 258 # 259 # } else { #string compare use the guts of str_cmp to accomplish this 260 # 261 # my $normalizedCorrectAnswer = $correctAnswer; 262 # # normalize the correct answer: 263 # $normalizedCorrectAnswer=~ s/s*$//; # remove trailing whitespace 264 # $normalizedCorrectAnswer=~ s/s+/ /g; # replace double spaces by single space 265 # $normalizedCorrectAnswer=~ tr/a-z/A-Z/; # Make letters uppercase 266 # $normalizedCorrectAnswer=~ s/^s*//; # remove initial spaces 267 # my $PGanswerMessage = ""; 268 # $answer_evaluator = sub { 269 # my $in = shift @_; 270 # my $originalAnswer = $in; 271 # $in =~ s/s*$//; # remove trailing whitespace 272 # $in =~ s/s+/ /g; # replace double spaces by single space 273 # $in =~ tr/a-z/A-Z/; # Make letters uppercase 274 # # why is there no removing of the initial spaces here? 275 # my $correctQ =0; 276 # $correctQ = 1 if $in eq $normalizedCorrectAnswer; 277 # ($correctQ,$correctAnswer,$originalAnswer,$PGanswerMessage); 278 # }; 279 # 280 # 281 # } 282 # $answer_evaluator; 283 #} 284 ############################## SCORING FILES ROUTINES ############################ 285 #sub recordProblemAnswer { 286 # my ($in, $num,$user, $psvn)=@_; 287 # # &attachProbSetRecord($psvn); 288 # my($setNumber)=&getSetNumber($psvn); 289 # my ($scoreFileName)="${databaseDirectory}$scoreFilePrefix$setNumber$dash${psvn}.sco"; 290 # unless (-e $scoreFileName) 291 # {&createFile($scoreFileName, $Global::sco_files_permission, $Global::numericalGroupID);} 292 # open(TEMP_FILE,">>$scoreFileName") || 293 # print "Couldn't record answer in $scoreFileName"; 294 # # my $time = time; # add time stamp -- should we make this human readable? 295 # my $time = &formatDateAndTime(time); # add time stamp 296 # 297 # print TEMP_FILE "$num $DELIM $in $DELIM $user $DELIM $time\n"; 298 # close(TEMP_FILE); 299 # if ($in eq 'Y') {&putProblemNumOfCorrectAns(&getProblemNumOfCorrectAns($num,$psvn)+1,$num,$psvn);} 300 # if ($in eq 'N') {&putProblemNumOfIncorrectAns(&getProblemNumOfIncorrectAns($num,$psvn)+1,$num,$psvn);} 301 # unless (defined(&getProblemStatus($num)) and (&getProblemStatus($num) eq 'Y')) { 302 # &putProblemStatus($in,$num,$psvn); 303 # } 304 # &detachProbSetRecord($psvn); 305 # }; 306 307 #sub getRecordedScores { 308 # 309 # my ($Yarrayref,$Narrayref,$psvn) = @_; 310 # &attachProbSetRecord($psvn); 311 # my $setNumber = &getSetNumber($psvn); 312 # &detachProbSetRecord($psvn); 313 # my ($scoreFileName)="${databaseDirectory}$scoreFilePrefix$setNumber$dash${psvn}.sco"; 314 # #print "Reading from file $scoreFileName\n" if $debugON; 315 # if ( open(SCORE_FILE,"<$scoreFileName") ) { 316 # while (<SCORE_FILE>) { 317 # my @temp=split(/$DELIM/,$_); 318 # if ($temp[1]=~/Y/) {$$Yarrayref[$temp[0]]++;} 319 # elsif ($temp[1]=~/N/) {$$Narrayref[$temp[0]]++;} 320 # else {wwerror("$0", "corrupted $scoreFileName");} 321 # }; 322 # close(SCORE_FILE); 323 # } else { 324 # warn "Warning: Couldn't open $scoreFileName. Will continue.\n"; 325 # } 326 # # OPERATES ON THE ARRAYS Yarray and Narray. 327 #} 328 329 sub round_score { 330 my $num = shift; 331 my $rounding_dem = 10**$Global::score_decimal_digits; 332 int($num*$rounding_dem + .5)/$rounding_dem; 333 } 334 335 ######################## END SCORING FILES ROUTINES ########################### 336 ############ 337 ### SMD - subroutine to get the number of answers from a specific file 338 ### - this handles questions which have more than one answer field. 339 ### - called with a problem number ($probNum) 340 ### - returns number of answers 341 #sub getNumberofAns 342 # { 343 # local($problemnumber)=@_; 344 # $numberofAnswers=0; 345 # local($filename)= &getProblemFileName($problemnumber); 346 # open(FILE, "${templateDirectory}$filename"); 347 # while(<FILE>) 348 # { 349 # if ($_ =~ /^\s*\[ans/) 350 # { 351 # $numberofAnswers++; 352 # } 353 # } 354 # close FILE; 355 # $numberofAnswers; 356 # } 357 ### GAGE 8/23/96 358 #sub getNumberofSubmittedAns { 359 # my $i = 1; 360 # while (defined($inputs{"answer$i"}) ) #inputs can't be sytactically local (using my) 361 # {$i++}; 362 # $i--; # the off-by-one problem 363 # $i; 364 365 ########### 366 367 sub readSetDef { 368 my ($fileName) = @_; 369 my $setNumber = ''; 370 my $shortFileName = fileFromPath($fileName); 371 if ($shortFileName =~ m|^set(\w+)\.def$|) {$setNumber = $1;} 372 else { 373 wwerror("$0", "The setDefinition file name must begin with <CODE>set</CODE> 374 and must end with <CODE>.def</CODE> . Every thing in between becomes the name of the set. 375 For example <CODE>set1.def</CODE>, <CODE>setExam.def</CODE>, and <CODE>setsample7.def</CODE> 376 define sets named <CODE>1</CODE>, <CODE>Exam</CODE>, and <CODE>sample7</CODE> respectively. The 377 filename, $shortFileName, you entered is not legal\n"); 378 } 379 380 my ($line,$name,$value,$attemptLimit); 381 open (SETFILENAME, "$fileName") or wwerror("$0", "Can't open file $fileName\n"); 382 my $setHeaderFileName = ''; 383 my $probHeaderFileName = ''; 384 my @problemList=(); 385 my @problemValueList=(); 386 my @problemAttemptLimitList=(); 387 my ($dueDate,$openDate,$answerDate); 388 my ($problemListref,$problemValueListref,$problemAttemptLimitListref); 389 while (<SETFILENAME>) { 390 chomp($line = $_); 391 $line =~ s|(#.*)||; ## don't read past comments 392 unless ($line =~ /\S/) {next;} ## skip blank lines 393 $line =~ s|\s*$||; ## trim trailing spaces 394 $line =~ m|^\s*(\w+)\s*=\s*(.*)|; 395 if ($1 eq 'setNumber') {next;} 396 elsif ($1 eq 'paperHeaderFile') {$setHeaderFileName = $2;} 397 elsif ($1 eq 'screenHeaderFile') {$probHeaderFileName = $2;} 398 elsif ($1 eq 'dueDate') {$dueDate = $2;} 399 elsif ($1 eq 'openDate') {$openDate = $2;} 400 elsif ($1 eq 'answerDate') {$answerDate = $2;} 401 elsif ($1 eq 'problemList') {last;} 402 else {wwerror("$0", "readSetDef error, can't read the line: $line");} 403 } 404 405 my $time1 = &unformatDateAndTime($openDate); 406 my $time2 = &unformatDateAndTime($dueDate); 407 my $time3 = &unformatDateAndTime($answerDate); 408 if ($time2 < $time1 or $time3 < $time2) { 409 &Global::error('File.pl: readSetDef error', "The open date: $openDate, due date: $dueDate, and answer date: $answerDate 410 must be in chronologicasl order."); 411 } 412 413 $setHeaderFileName =~ s/(.*?)\s*$/$1/; #remove trailing white space 414 $probHeaderFileName =~ s/(.*?)\s*$/$1/; #remove trailing white space 415 416 # print "setNumber: $setNumber\ndueDate: $dueDate\nopenDate: $openDate\nanswerDate: $answerDate\n"; 417 while(<SETFILENAME>) { 418 chomp($line=$_); 419 $line =~ s/(#.*)//; ## don't read past comments 420 unless ($line =~ /\S/) {next;} ## skip blank lines 421 422 ($name, $value, $attemptLimit) = split (/\s*,\s*/,$line); 423 $name =~ s/\s*//g; 424 push(@problemList, $name); 425 $value = "" unless defined($value); 426 $value =~ s/[^\d]*//g; 427 unless ($value =~ /\d+/) {$value = 1;} 428 push(@problemValueList, $value); 429 $attemptLimit = "" unless defined($attemptLimit); 430 $attemptLimit =~ s/[^\d-]*//g; 431 unless ($attemptLimit =~ /\d+/) {$attemptLimit = -1;} 432 433 push(@problemAttemptLimitList, $attemptLimit); 434 } 435 close(SETFILENAME); 436 #print "problemList: @problemList\n"; 437 #print "problemValueList: @problemValueList\n"; 438 #print "problemAttemptLimitList: @problemAttemptLimitList\n"; 439 $problemListref = \@problemList; 440 $problemValueListref = \@problemValueList; 441 $problemAttemptLimitListref = \@problemAttemptLimitList; 442 ($setNumber,$setHeaderFileName,$probHeaderFileName,$dueDate,$openDate,$answerDate,$problemListref,$problemValueListref,$problemAttemptLimitListref); 443 } 444 445 sub max { ## find the max element of array 446 my $out = $_[0]; 447 my $num; 448 foreach $num (@_) { 449 if ((defined $num) and ($num > $out)) {$out = $num;} 450 } 451 $out; 452 } 453 454 sub min { ## find the max element of array 455 my $out = $_[0]; 456 my $num; 457 foreach $num (@_) { 458 if ((defined $num) and ($num < $out)) {$out = $num;} 459 } 460 $out; 461 } 462 463 sub getFieldLengths { 464 465 ## takes as a parameter the reference to a delimited array 466 ## (such as you would get by reading in a delimited file) 467 ## where each element is a line from a delimited file. 468 ## returns an array which holds 469 ## the maximum field lengths in the file. 470 471 my ($datFileArray_ref)=@_; 472 my($i); 473 my(@datArray,@fieldLength,@datFileArray, $line); 474 @fieldLength=(); 475 @datFileArray=@$datFileArray_ref; 476 477 foreach $line (@datFileArray) { ## read through file and get field lengths 478 unless ($line =~ /\S/) {next;} ## skip blank lines 479 chomp $line; 480 @datArray=&getRecord($line); 481 for ($i=0; $i <=$#datArray; $i++) { 482 $fieldLength[$i] = 0 unless defined $fieldLength[$i]; 483 $fieldLength[$i]=&max(length("$datArray[$i]"),$fieldLength[$i]); 484 } 485 } 486 return (@fieldLength); 487 } 488 489 490 sub columnArrayArrange { 491 492 ## takes as a parameter a delimited array 493 ## (such as you would get by reading in a delimited file) 494 ## where each element is a line from a delimited file. 495 496 # Outputs an array which adds 497 # extra space if necessary to the fields so that all columns line up. 498 # The widest field in any column will contain exactly 1 spaces at the 499 # end of the (non space characters of the) field. For example 500 # ",a very long field entry ," at one extreme and ", ," at the other 501 502 my @inFile=@_; 503 my($i,$tempFileName,$datString,$line); 504 my @outFile =(); 505 my(@fieldLength,@datArray); 506 $i=1; 507 508 @fieldLength=&getFieldLengths(\@inFile); 509 foreach $line (@inFile) { ## read through file array and get field lengths 510 unless ($line =~ /\S/) {next;} ## skip blank lines 511 chomp $line; 512 @datArray=&getRecord($line); 513 for ($i=0; $i <=$#datArray; $i++) { 514 $datArray[$i].=(" " x ($fieldLength[$i]+1-length("$datArray[$i]"))); 515 } 516 $datString=join("${DELIM}",@datArray); 517 push @outFile , "$datString\n"; 518 } 519 @outFile; 520 } 521 522 523 sub columnPrint { 524 525 # Takes two parameters. The first is the filename of the 526 # delimited input file. The second is the name of the 527 # output file (these names may be the same). The permissions 528 # and group of the output file will be the same as the 529 # input file 530 531 # Takes any delimited (with \$DELIM delimiters) file and adds 532 # extra space if necessary to the fields so that all columns line up. 533 # The widest field in any column will contain exactly 2 spaces at the 534 # end of the (non space characters 0f the) field. For example 535 # ",a very long field entry ," at one extreme and ", ," at the other 536 # 537 my($inFileName,$outFileName)=@_; 538 my($line); 539 540 my ($permission, $gid) = (stat($inFileName))[2,5]; 541 $permission = ($permission & 0777); ##get rid of file type stuff 542 543 open(INFILE,"$inFileName") or wwerror("$0","can't open $inFileName for reading"); 544 my @inFile=<INFILE>; 545 close(INFILE); 546 547 &createFile($outFileName, $permission, $gid); 548 549 my @outFile = &columnArrayArrange(@inFile); 550 551 open(OUTFILE,">$outFileName") or wwerror("$0","can't open $outFileName for writing"); 552 foreach $line (@outFile) {print OUTFILE $line;} 553 close(OUTFILE); 554 } 555 556 sub getRecord 557 558 # Takes a delimited line as a parameter and returns an 559 # array. Note that all white space is removed. If the 560 # last field is empty, the last element of the returned 561 # array is also empty (unlike what the perl split command 562 # would return). E.G. @lineArray=&getRecord(\$delimitedLine). 563 { 564 my $DELIM = $Global::delim; 565 my($line) = $_[0]; 566 my(@lineArray); 567 $line.='A'; # add 'A' to end of line so that 568 # last field is never empty 569 @lineArray = split(/\s*${DELIM}\s*/,$line); 570 $lineArray[$#lineArray] =~s/\s*A$//; # remove spaces and the 'A' from last element 571 $lineArray[0] =~s/^\s*//; # remove white space from first element 572 @lineArray; 573 } 574 575 576 577 578 sub delim2aa { 579 580 # Takes a delimited file as a parameter and returns an 581 # associative array with the first field as the key. 582 # Blank lines are skipped. White space is removed 583 584 my $fileName =$_[0]; 585 my(@dbArray,$key,%assocArray,$dbString); 586 open(FILE, "$fileName") or wwerror("$0","can't open $fileName"); 587 while (<FILE>) 588 { 589 unless ($_ =~ /\S/) {next;} ## skip blank lines 590 chomp; 591 @dbArray=&getRecord($_); 592 $key=shift(@dbArray); 593 $dbString=join("${DELIM}",@dbArray); 594 $assocArray{$key}=$dbString; 595 } 596 close(FILE); 597 %assocArray; 598 } 599 sub dropStatus 600 601 # Takes one parameter \$status and returns 1 if \$status matches a word in the 602 # \@STATUS_DROP global array, 0 otherwise. E.G. if ($dropStatus(\$status) {...} 603 # where \$status is the entry in the status field of the class list. \@STATUS_DROP 604 # is a global array defined in webwork.ph 605 { 606 my($tag) = 0; 607 my($status) = $_[0]; 608 my($statusItem); 609 foreach $statusItem (@STATUS_DROP) 610 { 611 if ($status =~ /^\s*$statusItem\s*$/i) {$tag = 1;} 612 613 } 614 $tag; 615 } 616 ##########################Basem's additions#################### 617 ##Gives a nice list of ALL problem sets using radio buttons as default. 618 ##So to make a form with radio buttoned sets, simply start the form on the 619 ##line before calling printProbSets and a line after for the submit. The 620 ##default CGI value that is passed is the probSetKey. To make it the setNo, 621 ##call this subroutine: &printProbSets("setNo") 622 ############################################################### 623 #sub printProbSets { 624 # my ($passFlag,$pHash)=@_; 625 # my %setNumberHash = %$pHash; 626 # my @sortedSetNumberKeys=&sortSetNamesByDueDate($pHash); 627 # my @problemDates = (); 628 # my $problemDateLine; 629 # my ( $probSetKey, $odts,$ddts,$adts,$timeNow,$DueDate,$AnswerDate,$OpenDate); 630 # my $sortedSetNumber; 631 # 632 # foreach $sortedSetNumber(@sortedSetNumberKeys) { 633 # $probSetKey=$setNumberHash{$sortedSetNumber}; 634 # &attachProbSetRecord($probSetKey); 635 # $odts=&getOpenDate($probSetKey); 636 # $ddts=&getDueDate($probSetKey); 637 # $adts=&getAnswerDate($probSetKey); 638 # $timeNow = time; 639 # 640 # $DueDate=&formatDateAndTime($ddts); 641 # $AnswerDate = &formatDateAndTime($adts); 642 # $OpenDate = &formatDateAndTime($odts); 643 # 644 # # prepare message based on current time relative to the Open, Due and Answer dates. 645 # $problemDateLine = ""; 646 # 647 # if ($passFlag eq "setNo") { 648 # $problemDateLine = "\n <INPUT NAME=\"setNo\" 649 # TYPE=\"radio\" VALUE=\"$sortedSetNumber\"> "; 650 # } 651 # else { 652 # $problemDateLine = "\n <INPUT NAME=\"probSetKey\" 653 # TYPE=\"radio\" VALUE=\"$setNumberHash{$sortedSetNumber}\"> "; 654 # } 655 # 656 # $problemDateLine .= "Problem Set Number $sortedSetNumber"; 657 # ($timeNow < $odts ) && do {$problemDateLine .= &beforeOpenDateMsg($OpenDate) . 658 # &problemDates($OpenDate,$DueDate,$AnswerDate);}; 659 # ( $odts <= $timeNow ) && ($timeNow < $ddts) && 660 # do {$problemDateLine .= &afterOpenDateMsg($DueDate) . 661 # &problemDates($OpenDate,$DueDate,$AnswerDate);}; 662 # ( $ddts <= $timeNow ) && ($timeNow < $adts) && 663 # do {$problemDateLine .= 664 # &afterDueDateMsg($AnswerDate) . &problemDates($OpenDate,$DueDate,$AnswerDate);}; 665 # ( $adts <= $timeNow ) && 666 # do {$problemDateLine .= 667 # &afterAnswerDateMsg .&problemDates($OpenDate,$DueDate,$AnswerDate);}; 668 # push (@problemDates, $problemDateLine); 669 # } 670 # 671 # print join("\n\n", @problemDates),"\n"; # include open/due/answer dates 672 # # prepared above 673 #} 674 # 675 ##########################Basem's additions#################### 676 ##Does the same thing as printProbSets but in the abbreviated style used in 677 ##welcome.pl 678 ############################################################### 679 #sub printProbSetsJR { 680 # my ($passFlag,$pHash)=@_; 681 # my %setNumberHash = %$pHash; 682 # my @sortedSetNumberKeys=sort keys(%setNumberHash); 683 # my @problemDates = (); 684 # my $problemDateLine; 685 # my ( $probSetKey, $odts,$ddts,$adts,$timeNow,$DueDate,$AnswerDate,$OpenDate); 686 # my $sortedSetNumber; 687 # 688 # foreach $sortedSetNumber(@sortedSetNumberKeys) { 689 # $probSetKey=$setNumberHash{$sortedSetNumber}; 690 # &attachProbSetRecord($probSetKey); 691 # $odts=&getOpenDate($probSetKey); 692 # $ddts=&getDueDate($probSetKey); 693 # $adts=&getAnswerDate($probSetKey); 694 # $timeNow = time; 695 # 696 # $DueDate=&formatDateAndTime($ddts); 697 # $AnswerDate = &formatDateAndTime($adts); 698 # $OpenDate = &formatDateAndTime($odts); 699 # 700 # # prepare message based on current time relative to the Open, Due and Answer dates. 701 # $problemDateLine = ""; 702 # 703 # if ($passFlag eq "setNo") { 704 # $problemDateLine = "\n <INPUT NAME=\"setNo\" 705 # TYPE=\"radio\" VALUE=\"$sortedSetNumber\"> "; 706 # } 707 # else { 708 # $problemDateLine = "\n <INPUT NAME=\"probSetKey\" 709 # TYPE=\"radio\" VALUE=\"$setNumberHash{$sortedSetNumber}\"> "; 710 # } 711 # 712 # $problemDateLine .= "Problem Set Number $sortedSetNumber"; 713 # 714 # ($timeNow < $odts ) && do {$problemDateLine .= &beforeOpenDateMsg($OpenDate);}; 715 # ( $odts <= $timeNow ) && ($timeNow < $ddts) && 716 # do {$problemDateLine .= &afterOpenDateMsg($DueDate);}; 717 # ( $ddts <= $timeNow ) && ($timeNow < $adts) && 718 # do {$problemDateLine .= &afterDueDateMsg($AnswerDate);}; 719 # ( $adts <= $timeNow ) && 720 # do {$problemDateLine .= &afterAnswerDateMsg;}; 721 # 722 # 723 # push (@problemDates, $problemDateLine); 724 # } 725 #print join("\n\n", @problemDates),"\n"; # include open/due/answer dates 726 # # prepared above 727 #} 728 729 730 sub beforeOpenDateMsg { 731 my ($OpenDate) = @_; 732 my $out = " --- <em>Before open date</em> -- "; 733 $out .= "Open date is: $OpenDate <BR>"; 734 $out; 735 }; 736 sub afterOpenDateMsg { #and before Due Date 737 my ($DueDate) = @_; 738 my $out = " --- <em><B>OPEN</B></em>"; 739 $out .= " -- Due date is: $DueDate <BR>"; 740 $out; 741 }; 742 sub afterDueDateMsg { #and before AnswerDate 743 my ($AnswerDate) = @_; 744 my $out = " --- <em><B>CLOSED</B></em> --"; 745 $out .= " Answers available on: $AnswerDate <BR>"; 746 $out; 747 }; 748 sub afterAnswerDateMsg { 749 my $out = " --- <em><B>CLOSED</B></em> -- "; 750 $out .= " answers available.<BR>"; 751 $out; 752 }; 753 754 755 sub problemDates { 756 my ($OpenDate,$DueDate,$AnswerDate) = @_; 757 my $out = <<ENDproblemDatesHTML; 758 <PRE> 759 Open: $OpenDate 760 <B>Due: $DueDate</B> 761 Answer: $AnswerDate 762 </PRE> 763 ENDproblemDatesHTML 764 765 $out; 766 } 767 768 sub formatDateAndTime { 769 my ($timeStamp)=@_; 770 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = 771 localtime($timeStamp); 772 my $twelveHour; 773 if($min<10){$min= "0" . $min;} 774 775 if($hour==0){$twelveHour = 12 . ":" . $min . " AM";} 776 elsif($hour<12){$twelveHour= $hour . ":" . $min . " AM";} 777 elsif($hour==12){$twelveHour = $hour . ":" . $min . " PM";} 778 else {$twelveHour = ($hour-12) . ":" . $min . " PM";} 779 780 if($year>99){$year = $year -100;} 781 if($year<10){$year= "0" . $year;} 782 783 my $returnTimeString = ($mon+1) . "/" . $mday . "/" . $year . " at " . $twelveHour; 784 $returnTimeString; 785 } 786 787 788 sub unformatDateAndTime { 789 my ($string) = @_; 790 my $orgString =$string; 791 $string =~ s|^\s+||; 792 $string =~ s|\s+$||; 793 $string =~ s|at| at |i; ## OK if forget to enter spaces or use wrong case 794 $string =~ s|AM| AM|i; ## OK if forget to enter spaces or use wrong case 795 $string =~ s|PM| PM|i; ## OK if forget to enter spaces or use wrong case 796 $string =~ s|,| at |; ## start translating old form of date/time to new form 797 798 my($date,$at,$time,$AMPM) = split(/\s+/,$string); 799 unless ($time =~ /:/) { 800 { ##bare block for 'case" structure 801 $time =~ /(\d\d)(\d\d)/; 802 my $tmp_hour = $1; 803 my $tmp_min = $2; 804 if ($tmp_hour eq '00') {$time = "12:$tmp_min"; $AMPM = 'AM';last;} 805 if ($tmp_hour eq '12') {$time = "12:$tmp_min"; $AMPM = 'PM';last;} 806 if ($tmp_hour < 12) {$time = "$tmp_hour:$tmp_min"; $AMPM = 'AM';last;} 807 if ($tmp_hour < 24) { 808 $tmp_hour = $tmp_hour - 12; 809 $time = "$tmp_hour:$tmp_min"; 810 $AMPM = 'PM'; 811 } 812 } ##end of bare block for 'case" structure 813 814 } 815 816 my ($mday, $mon, $year, $wday, $yday,$sec, $pm, $min, $hour); 817 $sec=0; 818 $time =~ /^([0-9]+)\s*\:\s*([0-9]*)/; 819 $min=$2; 820 $hour = $1; 821 if ( $hour < 1 or $hour > 12 or $min < 0 or $min > 59) { 822 &Global::error('File.pl: unformatDateAndTime error', "Incorrect date/time format $orgString. Correct format is 9/13/02 at 12:15 PM"); 823 } 824 $pm = 0; 825 $pm = 12 if ($AMPM =~/PM/ and $hour < 12); 826 $hour += $pm; 827 $hour = 0 if ($AMPM =~/AM/ and $hour == 12); 828 $date =~ m!([0-9]+)\s*/\s*([0-9]+)/\s*([0-9]+)! ; 829 $mday =$2; 830 $mon=($1-1); 831 if ( $mday < 1 or $mday > 31 or $mon < 0 or $mon > 11) { 832 &Global::error('File.pl: unformatDateAndTime error', "Incorrect date/time format $orgString. Correct format is 9/13/02 at 12:15 PM"); 833 } 834 $year=$3; 835 $wday=""; 836 $yday=""; 837 timelocal ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday); 838 } 839 840 sub texInput 841 842 ## Similar to the TeX input command. Takes a filename (with or without extension) 843 ## which is assumed to be in the \$templateDirectory. 844 ## E.G. print OUTFILE &texInput("file.tex"); 845 ## or print OUTFILE &texInput("file"); 846 847 { 848 my $texInFile = $_[0]; 849 my $texString; 850 if ($texInFile eq "") { 851 $texString = ''; 852 } else { 853 unless ($texInFile =~ m#\.#) {$texInFile .= '.tex';} 854 open(TEX_IN_FILE,"${templateDirectory}$texInFile") || 855 &Global::error("File.pl: textInput error", " Can't open ${templateDirectory}$texInFile"); 856 my @texInputArray = <TEX_IN_FILE>; 857 close(TEX_IN_FILE); 858 $texString = join('',@texInputArray); 859 unless ($texString =~ /\n$/s) {$texString .= "\n";} 860 } 861 ## print "$texString"; 862 $texString; 863 } 864 865 866 867 868 869 # A very useful macro for making sure that all of the directories to a file have been constructed. 870 871 sub surePathToTmpFile { # constructs intermediate directories if needed beginning at ${Global::htmlDirectory}tmp/ 872 # the input path must be either the full path, or the path relative to this tmp sub directory 873 my $path = shift; 874 my $delim = &getDirDelim(); 875 my $tmpDirectory = getCourseTempDirectory(); 876 # if the path starts with $tmpDirectory (which is permitted but optional) remove this initial segment 877 $path =~ s|^$tmpDirectory|| if $path =~ m|^$tmpDirectory|; 878 $path = convertPath($path); 879 # find the nodes on the given path 880 my @nodes = split("$delim",$path); 881 # create new path 882 $path = convertPath("$tmpDirectory"); 883 884 while (@nodes>1 ) { 885 $path = convertPath($path . shift (@nodes) ."/"); 886 unless (-e $path) { 887 # system("mkdir $path"); 888 createDirectory($path,$Global::tmp_directory_permission, $Global::numericalGroupID) || 889 wwerror($0, "Failed to create directory $path","","",""); 890 891 } 892 893 } 894 $path = convertPath($path . shift(@nodes)); 895 896 # system(qq!echo "" > $path! ); 897 898 $path; 899 900 } 901 902 903 904 905 sub fileFromPath { 906 my $path = shift; 907 my $delim =&getDirDelim(); 908 $path = convertPath($path); 909 $path =~ m|([^$delim]+)$|; 910 $1; 911 912 } 913 914 sub directoryFromPath { 915 my $path = shift; 916 my $delim =&getDirDelim(); 917 $path = convertPath($path); 918 $path =~ s|[^$delim]*$||; 919 $path; 920 } 921 922 sub createDirectory 923 { 924 my ($dirName, $permission, $numgid) = @_; 925 mkdir($dirName, $permission) or 926 wwerror("$0: createDirectory error", " Can't do mkdir($dirName, $permission)"); 927 chmod($permission, $dirName) or 928 wwerror("$0: createDirectory error", " Can't do chmod($permission, $dirName)"); 929 unless ($numgid == -1) {chown(-1,$numgid,$dirName) or 930 wwerror("$0: createDirectory error", " Can't do chown(-1,$numgid,$dirName)");} 931 } 932 use Cwd; 933 sub createFile { 934 my ($fileName, $permission, $numgid) = @_; 935 # my $decimal_per = sprintf "%lo", $permission; 936 # print "\n IN createFile: file is $fileName, permission is $decimal_per, gid is $numgid\n"; 937 938 open(TEMPCREATEFILE, ">$fileName") || 939 wwerror("File.pl: createFile error", " Can't open $fileName"); 940 my @stat = stat TEMPCREATEFILE; 941 close(TEMPCREATEFILE); 942 943 ## if the owner of the file is running this script (e.g. when the file is first created) 944 ## set the permissions and group correctly 945 if ($< == $stat[4]) { 946 # my $oldDirectory = cwd(); 947 # warn " old directory is $oldDirectory<BR>\n"; 948 # my $newDirectory = $fileName; 949 # $newDirectory =~ s|/[^/]+$||; 950 # warn " new directory is $newDirectory<BR>\n"; 951 # $fileName =~ m|([^/]+$)|; 952 # my $newFileName = $1; 953 # warn "new File name = $newFileName<BR>\n"; 954 # chdir $newDirectory; 955 # warn "changing to directory =" .cwd() ."<BR>\n"; 956 # 957 #chmod(0777,$fileName); 958 my $tmp = chmod($permission,$fileName) or 959 warn("File.pl: createFile error", " Can't do chmod($permission, $fileName)"); 960 chown(-1,$numgid,$fileName) or 961 warn("File.pl: createFile error", " Can't do chown($numgid, $fileName)"); 962 # #warn "foo is readable<BR>\n" if -w 'foo.gif'; 963 # #warn "chmod =" . chmod($permission,$newFileName) || 964 # # warn("File.pl: createFile error", " Can't do chmod($permission, $newFileName)"); 965 # #chdir $oldDirectory; 966 # #warn "changed back to directory =" .cwd() ."<BR>\n"; 967 } 968 } 969 970 sub rmDirectoryAndFiles 971 { 972 my ($PROBDIR) =@_; 973 my @allfiles = (); 974 opendir( DIRHANDLE, "$PROBDIR") || warn qq/Can't read directory $PROBDIR $!/; 975 @allfiles = map "$PROBDIR$_", grep( !/^\.\.?$/, readdir DIRHANDLE); 976 closedir(DIRHANDLE); 977 # print "unlinking<BR>",join("<BR>", @allfiles),"<P>"; 978 unlink(@allfiles); 979 # print "removing directory $PROBDIR <P>"; 980 rmdir("$PROBDIR"); 981 } 982 983 984 985 # this returns an array of set names sorted by due date (with all open sets first). 986 # It is called by a reference to a hash with keys the Set Names and values psvn's 987 # such as returned by &getAllProbSetNumbersHash or &getAllSetNumbersForStudentLoginHash 988 989 sub sortSetNamesByDueDate { 990 my ($setNameHashref) = @_; 991 my %setNameHash = %$setNameHashref; 992 my ($setName,$psvn,$ddts,$timeNow); 993 my %dueTimes =(); 994 995 foreach $setName (keys %setNameHash) { 996 $psvn=$setNameHash{$setName}; 997 &attachProbSetRecord($psvn); 998 $ddts=&getDueDate($psvn); 999 $dueTimes{$setName} = $ddts; 1000 } 1001 1002 my @sortedSetNames = sort 1003 1004 ## Sort setnumbers by due date. Using an anonymous block so that 1005 ## dueTimes gets passes without making it global to FILE.pl or 1006 ## passing it to a sorting subroutine (can we pass this?) 1007 1008 { 1009 $timeNow = time; 1010 if ( ($dueTimes{$a} <= $timeNow) and ($dueTimes{$b} <= $timeNow) ) 1011 { 1012 $dueTimes{$a} <=> $dueTimes{$b} 1013 or 1014 $a cmp $b 1015 } 1016 elsif ( ($dueTimes{$a} > $timeNow) and ($dueTimes{$b} > $timeNow) ) 1017 { 1018 $dueTimes{$a} <=> $dueTimes{$b} 1019 or 1020 $a cmp $b 1021 } 1022 else 1023 { 1024 $dueTimes{$b} <=> $dueTimes{$a} 1025 } 1026 } 1027 1028 keys %setNameHash ; 1029 @sortedSetNames; 1030 } 1031 1032 sub checkClasslistFile { 1033 ## takes as parameters the number of fields and the full path name of 1034 ## the classlist file. Checks that the file iv valid, i.e. (1) all records 1035 ## have the same number of fields, (2) the first fields (studentID's, usually SS#'s) are 1036 ## all distinct and (3) the last fields (the loginID's) are all distinct, 1037 ## and (4) that studentID's and loginID's comtain only valid characters 1038 1039 my($noOfFields,$fileName)=@_; 1040 my $msg = htmlCheckClasslistFile($noOfFields,$fileName); 1041 unless ($msg eq 'OK') { 1042 &wwerror("$0","$msg"); 1043 } 1044 } 1045 1046 sub htmlCheckClasslistFile { 1047 ## takes as parameters the number of fields and the full path name of 1048 ## the classlist file. Checks that the file iv valid, i.e. (1) all records 1049 ## have the same number of fields, (2) the first fields (studentID's, usually SS#'s) are 1050 ## all distinct and (3) the last fields (the loginID's) are all distinct, 1051 ## and (4) that studentID's and loginID's comtain only valid characters and 1052 ## (5) that other fields do not contain bas chacters 1053 1054 my($noOfFields,$fileName)=@_; 1055 1056 open (FILE, "$fileName") or 1057 &wwerror("$0","can't open $fileName"); 1058 my @classList = <FILE>; 1059 close(FILE); 1060 1061 my $msg = checkClasslistArray($noOfFields, \@classList,$fileName); 1062 return $msg; 1063 } 1064 1065 sub checkClasslistArray { 1066 ## takes as parameters the number of fields and a ref to 1067 ## the classlist array. Checks that the file iv valid, i.e. (1) all records 1068 ## have the same number of fields, (2) the first fields (studentID's, usually SS#'s) are 1069 ## all distinct and (3) the last fields (the loginID's) are all distinct, 1070 ## and (4) that studentID's and loginID's comtain only valid characters and 1071 ## (5) that other fields do not contain bas chacters 1072 1073 my($noOfFields,$classListref,$fileName)=@_; 1074 my($noOfDelim,$dbString,$num,$i,@classList); 1075 my(@keyList); 1076 my $msg =''; 1077 $noOfDelim = $noOfFields -1; 1078 1079 @classList = @$classListref; 1080 1081 foreach $dbString (@classList) { 1082 unless ($dbString =~ /\S/) {next;} 1083 chomp $dbString; 1084 $num=($dbString =~s/$DELIM/$DELIM/g); 1085 if ($num != $noOfDelim) { 1086 $num =$num+1; 1087 $msg = "\n\n The classlist file\n $fileName \n is corrupted. The record\n 1088 $dbString \n contains $num fields instead of $noOfFields fields. \nYou 1089 must correct this and then run this script again. 1090 \n\n"; 1091 return $msg; 1092 } 1093 } 1094 my (@SSList, @loginList); 1095 @SSList=(); @loginList=(); 1096 foreach $dbString (@classList) { 1097 unless ($dbString =~ /\S/) {next;} 1098 chomp $dbString; 1099 my @classListRecord=&getRecord($dbString); 1100 my ($studentID, $lastName, $firstName, $status, $comment, $section,$recitation, $email_address, $login_name) 1101 = @classListRecord; 1102 # next if &dropStatus($status); ## ignore students who have dropped 1103 unless ($studentID =~ /^[\w\-\.]+$/) { 1104 $msg = "\n\n The classlist file\n$fileName\n\n is corrupted. The record 1105 \n$dbString \n contains the invalid studentID: $studentID 1106 \n studentID's can contain only upper and lower case letters, digits, -, dot('.'), and _ 1107 \n You must correct this and then run this script again.\n\n"; 1108 return $msg; 1109 } 1110 unless ($login_name =~ /^[\w\-\.]+$/) { 1111 $msg = "\n\n The classlist file\n$fileName\n\n is corrupted. The record 1112 \n$dbString \n contains the invalid loginName: $login_name 1113 \n loginName's can contain only upper and lower case letters, digits, -, dot('.'), and _ 1114 \n You must correct this and then run this script again.\n\n"; 1115 return $msg; 1116 } 1117 1118 ## test entries for bad characters. 1119 my @entries = ($lastName, $firstName, $status, $comment, $section,$recitation, $email_address); 1120 my $item =''; 1121 foreach $item (@entries) { 1122 my $msg = test_entry($item); 1123 unless ($msg eq 'OK') {return $msg;} 1124 } 1125 1126 push(@SSList,$studentID); 1127 push(@loginList,$login_name); 1128 } 1129 @SSList = sort(@SSList); 1130 for ($i=0; $i < @SSList-1; $i++) { 1131 if ($SSList[$i] eq $SSList[$i+1]) { 1132 $msg = "\n\n The classlist file\n$fileName\n\n is corrupted. Duplicate studentID's equal 1133 to $SSList[$i] in\n $fileName\nYou must correct this and then run this script again.\n\n"; 1134 return $msg; 1135 } 1136 } 1137 @loginList = sort(@loginList); 1138 for ($i=0; $i < @loginList-1; $i++) { 1139 if ($loginList[$i] eq $loginList[$i+1]) { 1140 $msg ="\n\n The classlist file\n$fileName\n\n is corrupted. Duplicate loginNames equal 1141 to $loginList[$i] in\n 1142 $fileName\nYou must correct this and then run this script again.\n\n"; 1143 return $msg; 1144 } 1145 } 1146 $msg ='OK'; 1147 return $msg; 1148 } 1149 1150 ### macros for writing and reading html tables 1151 1152 sub array2htmlRow { 1153 ## The parameter is an array whose entries will beccome elements of a row 1154 ## in an html table. The output is a string formated the same way Excel 1155 ## formats html tables: numbers aligned right, other things left. 1156 1157 my @inArray = @_; 1158 my ($item,$align); 1159 my $outString =''; 1160 $outString = join '','<TR ALIGN="left" VALIGN="bottom">',"\n"; 1161 foreach $item (@inArray) { 1162 unless ($item =~ /\S/) {$item = ' ';} 1163 if ($item =~/^[\d\.]+$/){$align ='RIGHT'} else {$align ='LEFT'} 1164 $outString .= join '','<TD ALIGN=',$align,'>',"\n"; 1165 $outString .= join '',$item,'</TD>',"\n"; 1166 } 1167 $outString .= join '','</TR>',"\n"; 1168 } 1169 1170 sub array2htmlRowForm { 1171 ## The parameter is an array whose first entry is the row number (1, 2, etc) 1172 ## and remaining entries will become elements of a row 1173 ## in an html table. 1174 1175 my ($row,@inArray) = @_; 1176 my ($item,$size); 1177 my $outString =''; 1178 my $col =1; 1179 $outString = join '','<TR ALIGN=LEFT VALIGN=BOTTOM>',"\n"; 1180 foreach $item (@inArray) { 1181 unless ($item =~ /\S/) {$item = ' ';} 1182 $size = length($item); 1183 $outString .= join '','<TD>',"\n"; 1184 $outString .= join '','<INPUT TYPE="TEXT" SIZE = ', $size, ' NAME="',"row${row}col$col",'" VALUE="',"$item",'"> </TD>',"\n"; 1185 1186 $col++; 1187 } 1188 $outString .= join '','</TR>',"\n"; 1189 } 1190 1191 sub delimitedArray2htmlTable { 1192 1193 # Takes a ref to an array whose elements are rows of a delimited file 1194 # and outputs a string containing 1195 # an html table version of the array suitable for viewing and editing 1196 # in Excel or a browser such as Netscape/Communicator. If the second 1197 # optional parameter is 'htmlform', the output is an html form. Otherwise 1198 # the output is a plain html document. 1199 # Blank lines are skipped. White space is removed. 1200 1201 my ($inArrayref, $type) = @_; 1202 1203 ## setup html header and initial table stuff 1204 my $rowString; 1205 my $outString = "<Table border>\n"; 1206 1207 ## translate data from delimited format to html format 1208 my $row =1; 1209 foreach (@$inArrayref) 1210 { 1211 unless ($_ =~ /\S/) {next;} ## skip blank lines 1212 chomp; 1213 if ( (defined $type) and ($type eq 'htmlform')) {$rowString = &array2htmlRowForm($row, &getRecord($_));} 1214 else {$rowString = &array2htmlRow(&getRecord($_));} 1215 $outString .= $rowString; 1216 $row++; 1217 } 1218 1219 ## setup html end table 1220 $outString .= join '','</Table>',"\n" ; 1221 } 1222 1223 sub delimitedArray2html { 1224 1225 # Takes a ref to an array whose elements are rows of a delimited file 1226 # and outputs a string containing 1227 # an html version of the array suitable for viewing and editing 1228 # in Excel or a browser such as Netscape/Communicator. The $label is the name 1229 # appearing at the top of the form or page. If the third 1230 # optional parameter is 'htmlform', the output is an html form. Otherwise 1231 # the output is a plain html document. 1232 # Blank lines are skipped. White space is removed. 1233 1234 my ($inArrayref, $label, $type) = @_; 1235 1236 ## setup html header and initial table stuff 1237 my $rowString; 1238 my $outString = join '','<HTML>',"\n" ,'<HEAD>',"\n", '<TITLE>'; 1239 $outString .= join '',$label,'</TITLE>',"\n",'</HEAD>',"\n",'<BODY>',"\n"; 1240 $outString .= join '','<H1><CENTER>',$label,'</CENTER></H1>',"\n"; 1241 $outString .= &delimitedArray2htmlTable($inArrayref, $type); 1242 1243 ## setup html footer stuff 1244 $outString .= join '','</BODY>',"\n", '</HTML>'; 1245 } 1246 1247 1248 sub delim2html { 1249 1250 # Takes a delimited file name as input and outputs a string containing 1251 # an html version of the input file suitable for viewing and editing 1252 # in Excel or a browser such as Netscape/Communicator. If the second 1253 # optional parameter is 'htmlform', the output is an html form. Otherwise 1254 # the output is a plain html document. 1255 # Blank lines are skipped. White space is removed. 1256 1257 my ($inFileName,$type) = @_; 1258 1259 my $shortFileName = $inFileName; 1260 unless (defined($type) and $type eq 'htmlform') {$type = 'html';} 1261 if ($shortFileName =~ m|$dd|) { 1262 $shortFileName =~ m|$dd([^$dd]*)$|; ## extract filename from full path name 1263 $shortFileName = $1; 1264 } 1265 $shortFileName =~ s|\..*||; ## remove extension 1266 open(INFILE, $inFileName) || wwerror("$0", "can't open $inFileName"); 1267 my @outArray = <INFILE>; 1268 close(INFILE); 1269 my $outString = delimitedArray2html(\@outArray,$shortFileName,$type); 1270 $outString; 1271 } 1272 1273 1274 1275 1276 sub htmlPage2htmlTable { ## Takes a string which contains a full html page 1277 ## containing a single table and removes all the 1278 ## header and footer material leaving only the row 1279 ## entries between <table> and </table>. Also removes all the 1280 ## <font ...> and </font> stuff from within the table. 1281 ## The cleaned up string is returned. 1282 1283 my ($inString) = @_; 1284 $inString =~ s|^.*<\s*table.*?>||is; ## remove <table> and stuff before 1285 $inString =~ s|<\s*/table\s*>.*?$||is; ## remove </table> and stuff after 1286 $inString =~ s|<\s*/*font.*?>||gis; ## remove font stuff 1287 $inString =~ s|>[^>]*$|>|s; ## remove any stuff after last > 1288 $inString; 1289 } 1290 1291 sub htmlTable2delim { ## Takes a string (e.g. output from htmlPage2htmlTable) which 1292 ## contains the rows from an html table and returns a string 1293 ## containing the table data in delimited format. 1294 1295 my ($inString) = @_; 1296 my ($outString, $item, $rowString); 1297 $outString =''; 1298 while ($inString){ 1299 $inString =~ s|^(.*?<\s*/tr\s*>)||is; # get next row 1300 $item = $1; 1301 $rowString = join("${DELIM}",&htmlRow2array($item)); 1302 $outString .= join '', $rowString, " \n"; 1303 } 1304 $outString; 1305 } 1306 1307 sub htmlForm2delim { ## Takes a reference to the associtive array of inputs from 1308 ## a form. The $inputs{row5col8} is the element for the 5th row 1309 ## and 8 column. It is assumed the input is a rectangular array 1310 ##Returns a string containing the table data in delimited format. 1311 1312 my ($inputsref) = @_; 1313 my %inputs = %$inputsref; 1314 my ($item, $index,$row,$col); 1315 my $maxCol = 1; 1316 my $maxRow = 1; 1317 my @rowColIndex = grep /^row\d+col\d+$/, keys %inputs; 1318 foreach $index (@rowColIndex) { 1319 $index =~ /^row(\d+)col(\d+)$/; 1320 if ($1 > $maxRow) {$maxRow = $1}; 1321 if ($2 > $maxCol) {$maxCol = $2}; 1322 } 1323 1324 my @outArray =(); 1325 my $rowString =''; 1326 my @rowArray= (); 1327 1328 for $row (1..$maxRow) { 1329 @rowArray= (); 1330 for $col (1..$maxCol) {push @rowArray, $inputs{"row${row}col${col}"};} 1331 $rowString = join("${DELIM}",@rowArray); 1332 push (@outArray,$rowString); 1333 } 1334 @outArray = &columnArrayArrange(@outArray); ## line up columns 1335 my $outString = join('',@outArray); 1336 $outString; 1337 } 1338 1339 1340 1341 sub htmlRow2array { 1342 ## The parameter is a string "<TR ... /TR>" containing one row 1343 ## in an html table. The output is an array containing the entries 1344 ## contained in that row. 1345 1346 my ($inString) = @_; 1347 $inString =~ s|^.*<\s*tr.*?>||is; ## remove <tr> and stuff before 1348 $inString =~ s|<\s*/tr\s*>.*$||is; ## remove </tr> and stuff after 1349 $inString =~ s|>[^>]*$|>|s; ## remove any stuff after last > 1350 1351 my @outArray =(); 1352 my $item; 1353 while ($inString){ 1354 $inString =~ s%^(.*?<)\s*/t[d|h]\s*>%%is; # get next entry 1355 $item = $1; 1356 $item =~ m|>\s*(.*?)<|is; # get entry 1357 $item =$1; 1358 $item =~ s|\s*$||; # remove trailing spaces 1359 if (($item eq ' ') or ($item eq '')) {$item =' '} 1360 push @outArray, $item; 1361 } 1362 @outArray; 1363 } 1364 1365 1366 ## this subroutine prints all environment variables. 1367 ## adapted from http://www.cgi-resources.com/Documentation/Environment_Variables/ 1368 ## takes parameters html_top, html_bot which print html top and bottom matter if set 1369 sub printEnvVars { 1370 1371 my ($top, $bot) = @_; 1372 my ($bigcontent, @content, $content,$name,$value,%input,$tvar,$key); 1373 # First, if METHOD=GET we grab the environment variable 1374 # containing the Query_String - otherwise we grab the 1375 # environment variable Content_Length. 1376 if ($ENV{'REQUEST_METHOD'} eq "GET") { 1377 $bigcontent = $ENV{'QUERY_STRING'}; 1378 } # Close if bracket 1379 else { 1380 read(STDIN, $bigcontent, $ENV{'CONTENT_LENGTH'}); 1381 } # Close else bracket 1382 # bigcontent now contains a long string which is broken by 1383 # ampersands between the various form elements. So let's split 1384 # it and load it into an array 1385 @content = split(/&/, $bigcontent); 1386 # But we aren't done yet. All of the spaces in the form data 1387 # were replaced by pluses. Other non-alpha characters except 1388 # equal signs were replaced by their hex values. So now we 1389 # need to step through the array and translate them back into 1390 # their "sent" form. 1391 foreach $content (@content) { 1392 # Split HTML form's "NAME" and "VALUE" at equal signs 1393 ($name, $value) = split(/=/, $content); 1394 # Replace the pluses with spaces 1395 $value =~ tr/+/ /; 1396 # Translate the hex (now preceded by percent sign) into ASCII 1397 $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; 1398 # And finish by loading input variables for use in program. 1399 # You call it by $input{'formvarname'} to get the literal 1400 # that the user typed into that field on the Form. 1401 $input{$name} = $value; 1402 } # Close bracket for foreach loop 1403 1404 # Tell the server that we are going to send it to user's browser 1405 if ($top eq 'html_top') {print "Content-type: text/html\n\n"; 1406 # So we don't have to type backslashes everywhere before reserved 1407 # characters in the HTML, we use this so the PERL compiler will 1408 # know that what follows is literal (except for variable names) 1409 # But be careful - still need backslash in front of literal at 1410 # signs, dollar signs, etc., since PERL assumes a variable name 1411 # follows these characters. 1412 print <<ENDOFTEXT; 1413 <HTML><HEAD><TITLE>Environment Variable 1414 Test</TITLE></HEAD> 1415 <BODY BGCOLOR="#FFFFFF"> 1416 1417 ENDOFTEXT 1418 } 1419 # Now, simply sort and print the names and values of each of the 1420 # environment variables from the keyed array to browser window 1421 foreach $key (sort keys(%ENV)) {print 1422 "<B>$key:<\/B>$ENV{$key}<BR>";} 1423 1424 if ($bot eq 'html_bot') { 1425 print <<ENDOFTEXT; 1426 1427 <P> 1428 </BODY> 1429 </HTML> 1430 ENDOFTEXT 1431 1432 } 1433 } 1434 1435 sub backupFile { 1436 ## takes as a parameter the full filename 1437 ## makes upto three backups of file with x, y, or z appended to filename where x 1438 ## the most recent backup 1439 1440 my $fileName =$_[0]; 1441 my $orgFileName = "$fileName"; 1442 my ($ext, $fnMinusExt,$noPeriod); 1443 if (! ($orgFileName =~ m|\.|)) { 1444 $noPeriod =1; 1445 $fnMinusExt = $orgFileName; 1446 $ext =''; 1447 } 1448 else { 1449 $noPeriod =0; 1450 $orgFileName =~ m|^(.*)\.([^\.]*)$|; 1451 $fnMinusExt = $1; 1452 $ext = $2; 1453 } 1454 my $period = '.'; 1455 $period = '' if $noPeriod; 1456 if (-e "${fnMinusExt}y${period}${ext}") { 1457 rename("${fnMinusExt}y${period}$ext","${fnMinusExt}z${period}$ext") or 1458 &wwerror("$0","can't rename ${fnMinusExt}y${period}$ext"); 1459 } 1460 1461 if (-e "${fnMinusExt}x${period}$ext") { 1462 rename("${fnMinusExt}x${period}$ext","${fnMinusExt}y${period}$ext") or 1463 &wwerror("$0","can't rename ${fnMinusExt}x${period}$ext"); 1464 } 1465 1466 if (-e "${fnMinusExt}${period}$ext") { 1467 rename("${fnMinusExt}${period}$ext","${fnMinusExt}x${period}$ext") or 1468 &wwerror("$0","can't rename ${fnMinusExt}${period}$ext"); 1469 } 1470 } 1471 1472 sub stripWhiteSpace { ## strip initial and trailing whitespace 1473 my $string = $_[0]; 1474 $string =~ s/\s*$//; # remove trailing whitespace 1475 $string =~ s/^\s*//; # remove initial spaces 1476 $string; 1477 } 1478 1479 sub test_entry{ ## check for bad characters. & and = are used as delimiters 1480 ## in databases. DELIM (usually a coma) is used in csv files 1481 my $entry = shift; 1482 my $msg = 'OK'; 1483 if ($entry =~ /[=&$DELIM]/) { 1484 $msg = " The entry: $entry is invalid. 1485 An entry can not contain any of the following characters: $DELIM & = 1486 You must go back and correct this.\n"; 1487 } 1488 $msg; 1489 } 1490 1491 sub testNewStudentLogin { 1492 my $login_name = shift; 1493 my $newStudentID = shift; 1494 my $msg = 'OK'; 1495 unless ($login_name =~ /^[\w\-\.]+$/) { 1496 $msg = " The login name: $login_name is invalid. 1497 Login name's can contain only upper and lower case letters, digits, -, dot('.'), and _ 1498 you must go back and correct this.\n"; 1499 return $msg; 1500 } 1501 my %currentLogins = %{getLoginName_StudentID_Hash()}; 1502 if (defined $currentLogins{$login_name}){ 1503 attachCLRecord($login_name); 1504 1505 my $studentLastName = CL_getStudentLastName($login_name); 1506 my $studentFirstName = CL_getStudentFirstName($login_name); 1507 my $studentID = CL_getStudentID($login_name); 1508 1509 $msg = " The login name: $login_name is already in use. 1510 It is assigned to $studentFirstName $studentLastName ($studentID). 1511 You must go back and choose a login name which is not yet being used.\n"; 1512 return $msg; 1513 } 1514 1515 ## check that if student login exists in webwork database, the studentID's match 1516 1517 if ( -e "${databaseDirectory}$Global::database" ){ 1518 my %loginName_StudentID_Hash_from_WW_DB =%{getLoginName_StudentID_Hash_from_WW_DB()}; 1519 if (defined $loginName_StudentID_Hash_from_WW_DB{$login_name}) { 1520 my $oldStudentID = $loginName_StudentID_Hash_from_WW_DB{$login_name}; 1521 unless ($newStudentID eq $oldStudentID) { 1522 my %setNumberHash = &getAllSetNumbersForStudentLoginHash($login_name); 1523 my @SetNumberKeys = keys(%setNumberHash); 1524 $msg = " The login name: $login_name is already in use in the webwork problem database. 1525 However, the new student ID ($newStudentID) does not match the old student ID ($oldStudentID). 1526 The following problem sets exist for $login_name $oldStudentID: 1527 Sets: @SetNumberKeys 1528 You have three choices. 1529 (1) Go back and use $oldStudentID for the student ID in which case the above sets will again be 1530 assigned to $login_name $oldStudentID. 1531 (2) Go back and choose a login name which is not yet being used. 1532 (3) Delete the problem sets listed above for $login_name $oldStudentID and then try again adding the student 1533 $login_name $newStudentID."; 1534 1535 return $msg; 1536 } 1537 } 1538 } 1539 $msg; 1540 } 1541 1542 1543 sub testNewStudentID { 1544 my $studentID = shift; 1545 my $newLogin_name = shift; 1546 my $msg ='OK'; 1547 unless ($studentID =~ /^[\w\-\.]+$/) { 1548 $msg = " The student ID: $studentID is invalid. 1549 student ID's can contain only upper and lower case letters, digits, -, dot('.'), and _ 1550 you must go back and correct this.\n"; 1551 return ($msg); 1552 } 1553 my %currentIDs = %{getStudentID_LoginName_Hash()}; 1554 1555 if (defined $currentIDs{$studentID}) { 1556 my $oldLogin = $currentIDs{$studentID}; 1557 attachCLRecord($oldLogin); 1558 1559 my $studentLastName = CL_getStudentLastName($oldLogin); 1560 my $studentFirstName = CL_getStudentFirstName($oldLogin); 1561 1562 1563 $msg = " The student ID: $studentID is already in use. 1564 It is assigned to $studentFirstName $studentLastName ($oldLogin). 1565 you must go back and choose a student ID which is not yet being used.\n"; 1566 return $msg; 1567 } 1568 1569 ## check that if student ID exists in webwork database, the student login's match 1570 1571 if ( -e "${databaseDirectory}$Global::database" ){ 1572 my %loginName_StudentID_Hash_from_WW_DB =%{getLoginName_StudentID_Hash_from_WW_DB()}; 1573 my %studentID_LoginName_Hash_from_WW_DB = reverse %loginName_StudentID_Hash_from_WW_DB; 1574 if (defined $studentID_LoginName_Hash_from_WW_DB{$studentID}) { 1575 my $oldLogin_name = $studentID_LoginName_Hash_from_WW_DB{$studentID}; 1576 unless ($newLogin_name eq $oldLogin_name) { 1577 my %setNumberHash = &getAllSetNumbersForStudentLoginHash($oldLogin_name); 1578 my @SetNumberKeys = keys(%setNumberHash); 1579 $msg = " The student ID: $studentID is already in use in the webwork problem database. 1580 However, the new student Login name ($newLogin_name) does not match the old student Login name ($oldLogin_name). 1581 The following problem sets exist for $oldLogin_name $studentID: 1582 Sets: @SetNumberKeys 1583 You have three choices. 1584 (1) Go back and use $oldLogin_name for the student login name in which case the above sets will again be 1585 assigned to $oldLogin_name $studentID. 1586 (2) Go back and choose a student ID which is not yet being used. 1587 (3) Delete the problem sets listed above for $oldLogin_name $studentID and then try again adding the student 1588 $newLogin_name $studentID."; 1589 1590 return $msg; 1591 } 1592 } 1593 } 1594 1595 1596 $msg; 1597 } 1598 1599 sub getClasslistFilesAndLabels { ## returns a two element array 1600 ## the 0th element is a ref to an array of files 1601 ## the 1st element is a ref to a hash of labels 1602 my $Course = shift; 1603 my $defaultClasslistFile = getCourseClasslistFile($Course); 1604 1605 ## find the available files 1606 1607 opendir CLASSLISTDIR, $templateDirectory or wweror($0,"Can't open directory $templateDirectory"); 1608 my @allFiles = grep !/^\./, readdir CLASSLISTDIR; 1609 closedir CLASSLISTDIR; 1610 1611 ## sort the files 1612 1613 my @classlistFiles = grep /\.lst$/,@allFiles; 1614 my @sortedNames = sort @classlistFiles; 1615 1616 ## put the default classlist file first if it exists 1617 my $shortFileName = $defaultClasslistFile; 1618 if ($shortFileName =~ m|$dd|) { 1619 $shortFileName =~ m|$dd([^$dd]*)$|; ## extract filename from full path name 1620 $shortFileName = $1; 1621 } 1622 my @newSortedNames = grep !/^$shortFileName$/, @sortedNames; 1623 if ($#newSortedNames != $#sortedNames) { 1624 unshift @newSortedNames,$shortFileName; 1625 @sortedNames = @newSortedNames; 1626 } 1627 1628 ## generate labels 1629 my %label_hash = (); 1630 1631 my ($ind,$date,$fileName,@stat); 1632 for $ind (@sortedNames) { 1633 $fileName = "${templateDirectory}$ind"; 1634 if (-e $fileName) { 1635 @stat = stat($fileName); 1636 $date = $stat[9]; 1637 $date = formatDateAndTime($date); 1638 $date =~ s|\s*at.*||; 1639 $label_hash{$ind} = "$ind --- Last Changed $date"; 1640 } 1641 } 1642 (\@sortedNames,\%label_hash); 1643 } 1644 1645 1646 1647 1648 1;
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |