[system] / trunk / webwork2 / lib / WeBWorK / ContentGenerator / Instructor / Scoring.pm Repository:
ViewVC logotype

View of /trunk/webwork2/lib/WeBWorK/ContentGenerator/Instructor/Scoring.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1413 - (download) (as text) (annotate)
Mon Jul 21 23:07:03 2003 UTC (9 years, 10 months ago) by malsyned
File size: 9002 byte(s)
Now generates scoring data files in response to requests from the set
list.  There is no confirmation that this has been done, yet, though.
If it returns an "empty" page, it worked, and you can see the scoring
files.

    1 ################################################################################
    2 # WeBWorK mod_perl (c) 2000-2002 WeBWorK Project
    3 # $Id$
    4 ################################################################################
    5 
    6 package WeBWorK::ContentGenerator::Instructor::Scoring;
    7 use base qw(WeBWorK::ContentGenerator::Instructor);
    8 
    9 =head1 NAME
   10 
   11 WeBWorK::ContentGenerator::Instructor::Scoring - Generate scoring data files
   12 
   13 =cut
   14 
   15 use strict;
   16 use warnings;
   17 use CGI qw();
   18 use WeBWorK::Utils qw(readFile formatDateTime);
   19 
   20 sub initialize {
   21   my ($self) = @_;
   22   my $r = $self->{r};
   23   my $ce = $self->{ce};
   24 
   25   my $scoringDir = $ce->{courseDirs}->{scoring};
   26   if (defined $r->param('scoreSelected')) {
   27     my @selected = $r->param('selectedSet');
   28     foreach my $setID (@selected) {
   29       my @scoringData = $self->scoreSet($setID);
   30       $self->writeCSV("$scoringDir/s${setID}scr.csv", @scoringData);
   31     }
   32   }
   33 }
   34 
   35 
   36 # If, some day, it becomes possible to assign a different number of problems to each student, this code
   37 # will have to be rewritten some.
   38 # $format can be any of "normal", "full", "info", or "totals".  An undefined value defaults to "normal"
   39 #   normal: student info, the status of each problem in the set, and a "totals" column
   40 #   full: student info, the status of each problem, and the number of correct and incorrect attempts
   41 #   info: student info columns only
   42 #   totals: total column only
   43 sub scoreSet {
   44   my ($self, $setID, $format) = @_;
   45   my $db = $self->{db};
   46   my @scoringData;
   47 
   48   $format = "normal" unless defined $format;
   49   $format = "normal" unless $format eq "full" or $format eq "totals" or $format eq "info";
   50   my $columnsPerProblem = $format eq "full" ? 3 : 1;
   51   my $setRecord = $db->getGlobalSet($setID);
   52   my %users;
   53   foreach my $userID ($db->listUsers) {
   54     my $userRecord = $db->getUser($userID);
   55     # The key is what we'd like to sort by.
   56     $users{$userRecord->student_id} = $userRecord;
   57   }
   58   my @problemIDs = $db->listGlobalProblems($setID);
   59 
   60   # Initialize a two-dimensional array of the proper size
   61   for (my $i = 0; $i < keys(%users) + 7; $i++) { # 7 is how many descriptive fields there are in each column
   62     push @scoringData, [];
   63   }
   64 
   65 
   66   unless ($format eq "totals") {
   67     $scoringData[0][0] = "NO OF FIELDS";
   68     $scoringData[1][0] = "SET NAME";
   69     $scoringData[2][0] = "PROB NUMBER";
   70     $scoringData[3][0] = "DUE DATE";
   71     $scoringData[4][0] = "DUE TIME";
   72     $scoringData[5][0] = "PROB VALUE";
   73   }
   74 
   75   my @userInfoColumnHeadings = ("STUDENT ID", "LAST NAME", "FIRST NAME", "SECTION", "RECITATION");
   76   my @userInfoFields = ("student_id", "last_name", "first_name", "section", "recitation");
   77   my @userKeys = sort keys %users;
   78 
   79   # Write identifying information about the users
   80   unless ($format eq "totals") {
   81     for (my $field=0; $field < @userInfoFields; $field++) {
   82       if ($field > 0) {
   83         for (my $i = 0; $i < 6; $i++) {
   84           $scoringData[$i][$field] = "";
   85         }
   86       }
   87       $scoringData[6][$field] = $userInfoColumnHeadings[$field];
   88       for (my $user = 0; $user < @userKeys; $user++) {
   89         my $fieldName = $userInfoFields[$field];
   90         $scoringData[7 + $user][$field] = $users{$userKeys[$user]}->$fieldName;
   91       }
   92     }
   93   }
   94   return @scoringData if $format eq "info";
   95 
   96   # Write the problem data
   97   my $dueDateString = formatDateTime($setRecord->due_date);
   98   my ($dueDate, $dueTime) = $dueDateString =~ m/^([^\s]*)\s*([^\s]*)$/;
   99   my $valueTotal = 0;
  100   my %userStatusTotals = ();
  101   for (my $problem = 0; $problem < @problemIDs; $problem++) {
  102     my $globalProblem = $db->getGlobalProblem($setID, $problemIDs[$problem]);
  103     my $column = 5 + $problem * $columnsPerProblem;
  104     unless ($format eq "totals") {
  105       $scoringData[0][$column] = "";
  106       $scoringData[1][$column] = $setRecord->set_id;
  107       $scoringData[2][$column] = $globalProblem->problem_id;
  108       $scoringData[3][$column] = $dueDate;
  109       $scoringData[4][$column] = $dueTime;
  110       $scoringData[5][$column] = $globalProblem->value;
  111       $scoringData[6][$column] = "STATUS";
  112       if ($format eq "full") { # Fill in with blanks, or maybe the problem number
  113         for (my $row = 0; $row < 6; $row++) {
  114           for (my $col = $column+1; $col <= $column + 2; $col++) {
  115             if ($row == 2) {
  116               $scoringData[$row][$col] = $globalProblem->problem_id;
  117             } else {
  118               $scoringData[$row][$col] = "";
  119             }
  120           }
  121         }
  122         $scoringData[6][$column + 1] = "#corr";
  123         $scoringData[6][$column + 2] = "#incorr";
  124       }
  125     }
  126     $valueTotal += $globalProblem->value;
  127     for (my $user = 0; $user < @userKeys; $user++) {
  128       my $userProblem = $db->getMergedProblem($users{$userKeys[$user]}->user_id, $setID, $problemIDs[$problem]);
  129       $userStatusTotals{$user} = 0 unless exists $userStatusTotals{$user};
  130       $userStatusTotals{$user} += $userProblem->status * $userProblem->value;
  131       unless ($format eq "totals") {
  132         $scoringData[7 + $user][$column] = $userProblem->status;
  133         if ($format eq "full") {
  134           $scoringData[7 + $user][$column + 1] = $userProblem->num_correct;
  135           $scoringData[7 + $user][$column + 2] = $userProblem->num_incorrect;
  136         }
  137       }
  138     }
  139   }
  140 
  141   # write the status totals
  142   unless ($format eq "full") { # Ironic, isn't it?
  143     my $totalsColumn = $format eq "totals" ? 0 : 5 + @problemIDs * $columnsPerProblem;
  144     $scoringData[0][$totalsColumn] = "";
  145     $scoringData[1][$totalsColumn] = $setRecord->set_id;
  146     $scoringData[2][$totalsColumn] = "";
  147     $scoringData[3][$totalsColumn] = "";
  148     $scoringData[4][$totalsColumn] = "";
  149     $scoringData[5][$totalsColumn] = $valueTotal;
  150     $scoringData[6][$totalsColumn] = "total";
  151     for (my $user = 0; $user < @userKeys; $user++) {
  152       $scoringData[7+$user][$totalsColumn] = $userStatusTotals{$user};
  153     }
  154   }
  155 
  156   return @scoringData;
  157 }
  158 
  159 # Reads a CSV file and returns an array of arrayrefs, each containing a
  160 # row of data:
  161 # (["c1r1", "c1r2", "c1r3"], ["c2r1", "c2r2", "c2r3"])
  162 sub readCSV {
  163   my ($self, $fileName) = @_;
  164   my @result = ();
  165   my @rows = split m/\n/, readFile($fileName);
  166   foreach my $row (@rows) {
  167     push @result, [split m/\s*,\s*/, $row];
  168   }
  169   return @result;
  170 }
  171 
  172 # Write a CSV file from an array in the same format that readCSV produces
  173 sub writeCSV {
  174   my ($self, $filename, @csv) = @_;
  175 
  176   my @lengths = ();
  177   for (my $row = 0; $row < @csv; $row++) {
  178     for (my $column = 0; $column < @{$csv[$row]}; $column++) {
  179       $lengths[$column] = 0 unless defined $lengths[$column];
  180       $lengths[$column] = length $csv[$row][$column] if length $csv[$row][$column] > $lengths[$column];
  181     }
  182   }
  183 
  184   open my $fh, ">", $filename;
  185   foreach my $row (@csv) {
  186     my @rowPadded = ();
  187     foreach (my $column = 0; $column < @$row; $column++) {
  188       push @rowPadded, $self->pad($row->[$column], $lengths[$column] + 1);
  189     }
  190     print $fh join(",", @rowPadded);
  191     print $fh "\n";
  192   }
  193   close $fh;
  194 }
  195 
  196 # As soon as backwards compatability is no longer a concern and we don't expect to have
  197 # to use old ww1.x code to read the output anymore, I recommend switching to using
  198 # these routines, which are more versatile and compatable with other programs which
  199 # deal with CSV files.
  200 sub readStandardCSV {
  201   my ($self, $fileName) = @_;
  202   my @result = ();
  203   my @rows = split m/\n/, readFile($fileName);
  204   foreach my $row (@rows) {
  205     push @result, [$self->splitQuoted($row)];
  206   }
  207   return @result;
  208 }
  209 
  210 sub writeStandardCSV {
  211   my ($self, $filename, @csv) = @_;
  212   open my $fh, ">", $filename;
  213   foreach my $row (@csv) {
  214     print $fh (join ",", map {$self->quote($_)} @$row);
  215     print $fh "\n";
  216   }
  217   close $fh;
  218 }
  219 
  220 ###
  221 
  222 # This particular unquote method unquotes (optionally) quoted strings in the
  223 # traditional CSV style (double-quote for literal quote, etc.)
  224 sub unquote {
  225   my ($self, $string) = @_;
  226   if ($string =~ m/^"(.*)"$/) {
  227     $string = $1;
  228     $string =~ s/""/"/;
  229   }
  230   return $string;
  231 }
  232 
  233 # Should you wish to treat whitespace differently, this routine has been designed
  234 # to make it easy to do so.
  235 sub splitQuoted {
  236   my ($self, $string) = @_;
  237   my ($leadingSpace, $preText, $quoted, $postText, $trailingSpace, $result);
  238   my @result = ();
  239   my $continue = 1;
  240   while ($continue) {
  241     $string =~ m/\G(\s*)/gc;
  242     $leadingSpace = $1;
  243     $string =~ m/\G([^",]*)/gc;
  244     $preText = $1;
  245     if ($string =~ m/\G"((?:[^"]|"")*)"/gc) {
  246       $quoted = $1;
  247     }
  248     $string =~ m/\G([^,]*?)(\s*)(,?)/gc;
  249     ($postText, $trailingSpace, $continue) = ($1, $2, $3);
  250 
  251     $preText = "" unless defined $preText;
  252     $postText = "" unless defined $postText;
  253     $quoted = "" unless defined $quoted;
  254 
  255     if ($quoted and (not $preText and not $postText)) {
  256         $quoted =~ s/""/"/;
  257         $result = $quoted;
  258     } else {
  259       $result = "$preText$quoted$postText";
  260     }
  261     push @result, $result;
  262   }
  263   return @result;
  264 }
  265 
  266 # This particular quoting method does CSV-style (double a quote to escape it) quoting when necessary.
  267 sub quote {
  268   my ($self, $string) = @_;
  269   if ($string =~ m/[", ]/) {
  270     $string =~ s/"/""/;
  271     $string = "\"$string\"";
  272   }
  273   return $string;
  274 }
  275 
  276 sub pad {
  277   my ($self, $string, $padTo) = @_;
  278   my $spaces = $padTo - length $string;
  279   return $string . " "x$spaces;
  280 }
  281 
  282 sub maxLength {
  283   my ($self, $arrayRef) = @_;
  284   my $max = 0;
  285   foreach my $cell (@$arrayRef) {
  286     $max = length $cell unless length $cell < $max;
  287   }
  288   return $max;
  289 }
  290 
  291 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9