#!/usr/bin/perl
## $Id$
####################################################################
# Copyright @ 1995-1998 University of Rochester
# All Rights Reserved
####################################################################
# #############################################################
# #############################################################
# File: FILE.pl
# This contains the subroutines for creating problem files,
# recording scores, printing delimited files, etc.
# #############################################################
# #############################################################
use strict;
# Variables global to this file
my $scoringDirectory = getCourseScoringDirectory();
my $templateDirectory = getCourseTemplateDirectory();
my $scriptDirectory = getWebworkScriptDirectory();
my $databaseDirectory = getCourseDatabaseDirectory();
my $DELIM = $Global::delim;
my $scoreFilePrefix = $Global::scoreFilePrefix;
my $scoring_log = $Global::scoring_log;
my $dash = $Global::dash;
my $DAT = $Global::dat;
my @STATUS_DROP = @Global::statusDrop;
my $dd = getDirDelim();
# Subroutines:
#sub createProblem {
# my($mode,$probNum,$psvn,$courseName,$sourceref,$refSubmittedAnswers)=@_;
# my @out;
# #&attachProbSetRecord($psvn);
# my $fileName = &getProblemFileName($probNum,$psvn);
# #print "content-type: text/plain\n\ngetProblemFileName gives $fileName\n probNum =$probNum and psvn =$psvn\n";
# $fileName = $main::in{'probFileName'} if defined($main::in{'probFileName'});
# #print "now fileName gives $fileName\n probNum =$probNum and psvn =$psvn\n";
#
####Define global variables for the interpreter and seed random function
# #srand(&getProblemSeed($probNum,$psvn));
## print "\n\nContent-type: text/html\n\nERROR: createProblem: Submitted Answers list |$refSubmittedAnswers| not passed\n\n"
## unless defined($refSubmittedAnswers);
# defineProblemVars($mode,$probNum, $psvn,$courseName,$refSubmittedAnswers);
# @out = &createLines($mode,$fileName,$sourceref);
#}
#
#sub createProblem2 {
# my ($mode,$probNum,$psvn,$courseName,$sourceref,$refSubmittedAnswers)=@_;
# my %envir=defineProblemEnvir($mode,$probNum,$psvn,$courseName,$refSubmittedAnswers);
# #print %envir;
# createPGtext($sourceref,\%envir);
#}
#
########
## Where is createNumberedInsert used???
########
# sub createNumberedInsert {
# my($mode,$probNum,$psvn)=@_;
# my @out;
# # &attachProbSetRecord($psvn);
# my $fileName = &getInsertFileName($num,$psvn);
#
# ###Define global variables for the interpreter
# defineProblemVars($mode,$probNum, $psvn,$refSubmittedAnswers);
# @out = &createLines($mode, $fileName);
# }
#This subroutine has been substituted by createProblem, because it is
# virtually identical to it
#sub createInsert {
# my($mode,$fileName,$psvn,$courseName,$sourceref)=@_;
# my @out;
# # &attachProbSetRecord($psvn);
#
#
# ###Define global variables for the interpreter
# # This is for the probSet.pl page so $probNum is not well defined
# my $probNum = 0;
# defineProblemVars($mode,$probNum, $psvn,$courseName);
# @out = &createLines($mode,$fileName,$sourceref);
#}
#sub defineProblemVars {
# my ($mode,$probNum,$psvn,$courseName,$refSubmittedAnswers) = @_;
# @main::submittedAnswers = @$refSubmittedAnswers if defined($refSubmittedAnswers);
# $main::psvnNumber = $psvn;
# $main::psvn = $psvn;
# $main::studentName = &getStudentName($psvn);
# $main::studentLogin = &getStudentLogin($psvn);
# $main::sectionName = &getClassSection($psvn);
# $main::sectionNumber = &getClassSection($psvn);
# $main::setNumber = &getSetNumber($psvn);
# $main::questionNumber = $probNum;
# $main::probNum = $probNum;
# $main::openDate = &getOpenDate($psvn);
# $main::formatedOpenDate = &formatDateAndTime(&getOpenDate($psvn));
# $main::dueDate = &getDueDate($psvn);
# $main::formatedDueDate = &formatDateAndTime(&getDueDate($psvn));
# $main::answerDate = &getAnswerDate($psvn);
# $main::formatedAnswerDate = &formatDateAndTime(&getAnswerDate($psvn));
# $main::problemValue = &getProblemValue($probNum,$psvn);
# $main::fileName = &getProblemFileName($probNum,$psvn);
# $main::probFileName = &getProblemFileName($probNum,$psvn);
# $main::templateDirectory = &getCourseTemplateDirectory();
# $main::languageMode = $mode;
# $main::outputMode = $mode;
# $main::courseName = $courseName;
# $main::sessionKey = ( defined($main::in{'key'}) ) ?$main::in{'key'} : " ";
# #my $seed ;
# #if ( defined( $inputs{'seed'}) && $permissions == $Global::instructor_permissions ) {
# # $seed = $inputs{'seed'};
# #} else {
# # $seed = &getProblemSeed($probNum, $psvn);
# #}
# #$main::problemSeed = $seed;
# ##Move srand to PGeval, after unpacking it
# #srand($main::problemSeed);
#
#}
###no longer use this subroutine
###createPGtext calls PGeval directly
###the language is figured out in the processProblem.pl
#sub createLines {
#
# my ($mode,$fileName,$sourceref) = @_;
# my @out;
#
#
### Set current directory
## my $pathName = $fileName;
## $pathName =~ s|[^/]*$||;
## my $currentDirectory = ${templateDirectory} . ${pathName};
## chdir "$currentDirectory";
##
## if (! open(INPUT, "${templateDirectory}$fileName") ) {
### If the file can not be found and opened output an error message
## push(@out, "createLines: ERROR: Can't open filename ${templateDirectory}$fileName\n");
# }
# else {
#
#
### Determine language
# # print "content-type: text/plain\n\n fileName = $fileName\n";
# $fileName =~ /\.([^\.]*)$/;
# my $languageType = $1;
# #print "languageType=$languageType
\n";
### Call interpreter
# if ($languageType eq 'qz') {
# ##Assign INPUT to problem file
# require "${scriptDirectory}qz2sub.pl";;
# @out = &qz2($mode);
#
# @out = post_process_qz($mode, \@out);
# } elsif ($languageType eq 'pg') {
# #$languageMode = $mode; #Define global variables for the interpreter and seed random function
# @out =&PGeval($sourceref);
# } else {
# $out[0] = "ERROR: createLines: Don't understand languages with extension $languageType.
\n";
# }
## }
# @out;
#}
#
#sub post_process_qz {
# my ($mode,$refInput_lines) = @_;
# my $col = 70;
# my $len = 0.07*$col;
# my @output_lines = ();
# my $ansName = "";
# my $answerValue = "";
# my $ansCount = 0;
# my $line;
# foreach $line (@$refInput_lines) {
#
# if ($line =~ /^\[ans/i) {
#
# $ansCount++;
# $ansName = "answer" . "$ansCount";
# $answerValue = param("$ansName") if defined param("$ansName");
# #print "
$ansName
";
# if ($mode eq 'HTML') {
# push(@output_lines, qq(\n\n
set
and must end with .def . Every thing in between becomes the name of the set.
For example set1.def, setExam.def, and setsample7.def
define sets named 1, Exam, and sample7 respectively. The
filename, $shortFileName, you entered is not legal\n");
}
my ($line,$name,$value,$attemptLimit);
open (SETFILENAME, "$fileName") or wwerror("$0", "Can't open file $fileName\n");
my $setHeaderFileName = '';
my $probHeaderFileName = '';
my @problemList=();
my @problemValueList=();
my @problemAttemptLimitList=();
my ($dueDate,$openDate,$answerDate);
my ($problemListref,$problemValueListref,$problemAttemptLimitListref);
while ("; unlink(@allfiles); # print "removing directory $PROBDIR
";
rmdir("$PROBDIR");
}
# this returns an array of set names sorted by due date (with all open sets first).
# It is called by a reference to a hash with keys the Set Names and values psvn's
# such as returned by &getAllProbSetNumbersHash or &getAllSetNumbersForStudentLoginHash
sub sortSetNamesByDueDate {
my ($setNameHashref) = @_;
my %setNameHash = %$setNameHashref;
my ($setName,$psvn,$ddts,$timeNow);
my %dueTimes =();
foreach $setName (keys %setNameHash) {
$psvn=$setNameHash{$setName};
&attachProbSetRecord($psvn);
$ddts=&getDueDate($psvn);
$dueTimes{$setName} = $ddts;
}
my @sortedSetNames = sort
## Sort setnumbers by due date. Using an anonymous block so that
## dueTimes gets passes without making it global to FILE.pl or
## passing it to a sorting subroutine (can we pass this?)
{
$timeNow = time;
if ( ($dueTimes{$a} <= $timeNow) and ($dueTimes{$b} <= $timeNow) )
{
$dueTimes{$a} <=> $dueTimes{$b}
or
$a cmp $b
}
elsif ( ($dueTimes{$a} > $timeNow) and ($dueTimes{$b} > $timeNow) )
{
$dueTimes{$a} <=> $dueTimes{$b}
or
$a cmp $b
}
else
{
$dueTimes{$b} <=> $dueTimes{$a}
}
}
keys %setNameHash ;
@sortedSetNames;
}
sub checkClasslistFile {
## takes as parameters the number of fields and the full path name of
## the classlist file. Checks that the file iv valid, i.e. (1) all records
## have the same number of fields, (2) the first fields (studentID's, usually SS#'s) are
## all distinct and (3) the last fields (the loginID's) are all distinct,
## and (4) that studentID's and loginID's comtain only valid characters
my($noOfFields,$fileName)=@_;
my $msg = htmlCheckClasslistFile($noOfFields,$fileName);
unless ($msg eq 'OK') {
&wwerror("$0","$msg");
}
}
sub htmlCheckClasslistFile {
## takes as parameters the number of fields and the full path name of
## the classlist file. Checks that the file iv valid, i.e. (1) all records
## have the same number of fields, (2) the first fields (studentID's, usually SS#'s) are
## all distinct and (3) the last fields (the loginID's) are all distinct,
## and (4) that studentID's and loginID's comtain only valid characters and
## (5) that other fields do not contain bas chacters
my($noOfFields,$fileName)=@_;
open (FILE, "$fileName") or
&wwerror("$0","can't open $fileName");
my @classList = ',"\n";
foreach $item (@inArray) {
unless ($item =~ /\S/) {$item = ' ';}
if ($item =~/^[\d\.]+$/){$align ='RIGHT'} else {$align ='LEFT'}
$outString .= join '',' ',"\n";
}
sub array2htmlRowForm {
## The parameter is an array whose first entry is the row number (1, 2, etc)
## and remaining entries will become elements of a row
## in an html table.
my ($row,@inArray) = @_;
my ($item,$size);
my $outString ='';
my $col =1;
$outString = join '','',"\n";
$outString .= join '',$item,' ',"\n";
}
$outString .= join '','',"\n";
foreach $item (@inArray) {
unless ($item =~ /\S/) {$item = ' ';}
$size = length($item);
$outString .= join '',' ',"\n";
}
sub delimitedArray2htmlTable {
# Takes a ref to an array whose elements are rows of a delimited file
# and outputs a string containing
# an html table version of the array suitable for viewing and editing
# in Excel or a browser such as Netscape/Communicator. If the second
# optional parameter is 'htmlform', the output is an html form. Otherwise
# the output is a plain html document.
# Blank lines are skipped. White space is removed.
my ($inArrayref, $type) = @_;
## setup html header and initial table stuff
my $rowString;
my $outString = "',"\n";
$outString .= join '',' ',"\n";
$col++;
}
$outString .= join '','\n";
## translate data from delimited format to html format
my $row =1;
foreach (@$inArrayref)
{
unless ($_ =~ /\S/) {next;} ## skip blank lines
chomp;
if ( (defined $type) and ($type eq 'htmlform')) {$rowString = &array2htmlRowForm($row, &getRecord($_));}
else {$rowString = &array2htmlRow(&getRecord($_));}
$outString .= $rowString;
$row++;
}
## setup html end table
$outString .= join '','
',"\n" ;
}
sub delimitedArray2html {
# Takes a ref to an array whose elements are rows of a delimited file
# and outputs a string containing
# an html version of the array suitable for viewing and editing
# in Excel or a browser such as Netscape/Communicator. The $label is the name
# appearing at the top of the form or page. If the third
# optional parameter is 'htmlform', the output is an html form. Otherwise
# the output is a plain html document.
# Blank lines are skipped. White space is removed.
my ($inArrayref, $label, $type) = @_;
## setup html header and initial table stuff
my $rowString;
my $outString = join '','',"\n" ,'',"\n", '
',"\n";
$outString .= &delimitedArray2htmlTable($inArrayref, $type);
## setup html footer stuff
$outString .= join '','',"\n", '';
}
sub delim2html {
# Takes a delimited file name as input and outputs a string containing
# an html version of the input file suitable for viewing and editing
# in Excel or a browser such as Netscape/Communicator. If the second
# optional parameter is 'htmlform', the output is an html form. Otherwise
# the output is a plain html document.
# Blank lines are skipped. White space is removed.
my ($inFileName,$type) = @_;
my $shortFileName = $inFileName;
unless (defined($type) and $type eq 'htmlform') {$type = 'html';}
if ($shortFileName =~ m|$dd|) {
$shortFileName =~ m|$dd([^$dd]*)$|; ## extract filename from full path name
$shortFileName = $1;
}
$shortFileName =~ s|\..*||; ## remove extension
open(INFILE, $inFileName) || wwerror("$0", "can't open $inFileName");
my @outArray = and
. Also removes all the
## and stuff from within the table.
## The cleaned up string is returned.
my ($inString) = @_;
$inString =~ s|^.*<\s*table.*?>||is; ## remove and stuff before
$inString =~ s|<\s*/table\s*>.*?$||is; ## remove
and stuff after
$inString =~ s|<\s*/*font.*?>||gis; ## remove font stuff
$inString =~ s|>[^>]*$|>|s; ## remove any stuff after last >
$inString;
}
sub htmlTable2delim { ## Takes a string (e.g. output from htmlPage2htmlTable) which
## contains the rows from an html table and returns a string
## containing the table data in delimited format.
my ($inString) = @_;
my ($outString, $item, $rowString);
$outString ='';
while ($inString){
$inString =~ s|^(.*?<\s*/tr\s*>)||is; # get next row
$item = $1;
$rowString = join("${DELIM}",&htmlRow2array($item));
$outString .= join '', $rowString, " \n";
}
$outString;
}
sub htmlForm2delim { ## Takes a reference to the associtive array of inputs from
## a form. The $inputs{row5col8} is the element for the 5th row
## and 8 column. It is assumed the input is a rectangular array
##Returns a string containing the table data in delimited format.
my ($inputsref) = @_;
my %inputs = %$inputsref;
my ($item, $index,$row,$col);
my $maxCol = 1;
my $maxRow = 1;
my @rowColIndex = grep /^row\d+col\d+$/, keys %inputs;
foreach $index (@rowColIndex) {
$index =~ /^row(\d+)col(\d+)$/;
if ($1 > $maxRow) {$maxRow = $1};
if ($2 > $maxCol) {$maxCol = $2};
}
my @outArray =();
my $rowString ='';
my @rowArray= ();
for $row (1..$maxRow) {
@rowArray= ();
for $col (1..$maxCol) {push @rowArray, $inputs{"row${row}col${col}"};}
$rowString = join("${DELIM}",@rowArray);
push (@outArray,$rowString);
}
@outArray = &columnArrayArrange(@outArray); ## line up columns
my $outString = join('',@outArray);
$outString;
}
sub htmlRow2array {
## The parameter is a string "" containing one row
## in an html table. The output is an array containing the entries
## contained in that row.
my ($inString) = @_;
$inString =~ s|^.*<\s*tr.*?>||is; ## remove and stuff before
$inString =~ s|<\s*/tr\s*>.*$||is; ## remove and stuff after
$inString =~ s|>[^>]*$|>|s; ## remove any stuff after last >
my @outArray =();
my $item;
while ($inString){
$inString =~ s%^(.*?<)\s*/t[d|h]\s*>%%is; # get next entry
$item = $1;
$item =~ m|>\s*(.*?)<|is; # get entry
$item =$1;
$item =~ s|\s*$||; # remove trailing spaces
if (($item eq ' ') or ($item eq '')) {$item =' '}
push @outArray, $item;
}
@outArray;
}
## this subroutine prints all environment variables.
## adapted from http://www.cgi-resources.com/Documentation/Environment_Variables/
## takes parameters html_top, html_bot which print html top and bottom matter if set
sub printEnvVars {
my ($top, $bot) = @_;
my ($bigcontent, @content, $content,$name,$value,%input,$tvar,$key);
# First, if METHOD=GET we grab the environment variable
# containing the Query_String - otherwise we grab the
# environment variable Content_Length.
if ($ENV{'REQUEST_METHOD'} eq "GET") {
$bigcontent = $ENV{'QUERY_STRING'};
} # Close if bracket
else {
read(STDIN, $bigcontent, $ENV{'CONTENT_LENGTH'});
} # Close else bracket
# bigcontent now contains a long string which is broken by
# ampersands between the various form elements. So let's split
# it and load it into an array
@content = split(/&/, $bigcontent);
# But we aren't done yet. All of the spaces in the form data
# were replaced by pluses. Other non-alpha characters except
# equal signs were replaced by their hex values. So now we
# need to step through the array and translate them back into
# their "sent" form.
foreach $content (@content) {
# Split HTML form's "NAME" and "VALUE" at equal signs
($name, $value) = split(/=/, $content);
# Replace the pluses with spaces
$value =~ tr/+/ /;
# Translate the hex (now preceded by percent sign) into ASCII
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
# And finish by loading input variables for use in program.
# You call it by $input{'formvarname'} to get the literal
# that the user typed into that field on the Form.
$input{$name} = $value;
} # Close bracket for foreach loop
# Tell the server that we are going to send it to user's browser
if ($top eq 'html_top') {print "Content-type: text/html\n\n";
# So we don't have to type backslashes everywhere before reserved
# characters in the HTML, we use this so the PERL compiler will
# know that what follows is literal (except for variable names)
# But be careful - still need backslash in front of literal at
# signs, dollar signs, etc., since PERL assumes a variable name
# follows these characters.
print <
";}
if ($bot eq 'html_bot') {
print <