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

View of /trunk/webwork-modperl/lib/WeBWorK/ContentGenerator/Instructor/Scoring.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2778 - (download) (as text) (annotate)
Mon Sep 13 19:35:12 2004 UTC (8 years, 9 months ago) by sh002i
File size: 24225 byte(s)
timezone support

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator/Instructor/Scoring.pm,v 1.35 2004/06/14 22:18:16 toenail Exp $
    5 #
    6 # This program is free software; you can redistribute it and/or modify it under
    7 # the terms of either: (a) the GNU General Public License as published by the
    8 # Free Software Foundation; either version 2, or (at your option) any later
    9 # version, or (b) the "Artistic License" which comes with this package.
   10 #
   11 # This program is distributed in the hope that it will be useful, but WITHOUT
   12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   13 # FOR A PARTICULAR PURPOSE.  See either the GNU General Public License or the
   14 # Artistic License for more details.
   15 ################################################################################
   16 
   17 package WeBWorK::ContentGenerator::Instructor::Scoring;
   18 use base qw(WeBWorK::ContentGenerator::Instructor);
   19 
   20 =head1 NAME
   21 
   22 WeBWorK::ContentGenerator::Instructor::Scoring - Generate scoring data files
   23 
   24 =cut
   25 
   26 use strict;
   27 use warnings;
   28 use CGI qw();
   29 use WeBWorK::Utils qw(readFile);
   30 use WeBWorK::DB::Utils qw(initializeUserProblem);
   31 use WeBWorK::Timing;
   32 
   33 sub initialize {
   34   my ($self)     = @_;
   35   my $r          = $self->r;
   36   my $urlpath    = $r->urlpath;
   37   my $ce         = $r->ce;
   38   my $db         = $r->db;
   39   my $authz      = $r->authz;
   40   my $scoringDir = $ce->{courseDirs}->{scoring};
   41   my $courseName = $urlpath->arg("courseID");
   42   my $user       = $r->param('user');
   43 
   44   # Check permission
   45   return unless $authz->hasPermissions($user, "access_instructor_tools");
   46   return unless $authz->hasPermissions($user, "score_sets");
   47 
   48   if (defined $r->param('scoreSelected')) {
   49     my @selected               = $r->param('selectedSet');
   50     my @totals                 = ();
   51     my $recordSingleSetScores  = $r->param('recordSingleSetScores');
   52 
   53         $self->addmessage(CGI::div({class=>'ResultsWithError'},"You must select one or more sets for scoring")) unless @selected;
   54 
   55     # pre-fetch users
   56     $WeBWorK::timer->continue("pre-fetching users") if defined($WeBWorK::timer);
   57     my @Users = $db->getUsers($db->listUsers);
   58     my %Users;
   59     foreach my $User (@Users) {
   60       next unless $User;
   61       $Users{$User->user_id} = $User;
   62     }
   63     my @sortedUserIDs = sort { $Users{$a}->student_id cmp $Users{$b}->student_id }
   64       keys %Users;
   65     my @userInfo = (\%Users, \@sortedUserIDs);
   66     $WeBWorK::timer->continue("done pre-fetching users") if defined($WeBWorK::timer);
   67 
   68     my $scoringType            = ($recordSingleSetScores) ?'everything':'totals';
   69     my (@everything, @normal,@full,@info,@totalsColumn);
   70     @info             = $self->scoreSet($selected[0], "info", undef, @userInfo) if defined($selected[0]);
   71     @totals           = @info;
   72     my $showIndex     = defined($r->param('includeIndex')) ? defined($r->param('includeIndex')) : 0;
   73 
   74     foreach my $setID (@selected) {
   75         next unless defined $setID;
   76       if ($scoringType eq 'everything') {
   77         @everything = $self->scoreSet($setID, "everything", $showIndex, @userInfo);
   78         @normal = $self->everything2normal(@everything);
   79         @full = $self->everything2full(@everything);
   80         @info = $self->everything2info(@everything);
   81         @totalsColumn = $self->everything2totals(@everything);
   82         $self->appendColumns(\@totals, \@totalsColumn);
   83         $self->writeCSV("$scoringDir/s${setID}scr.csv", @normal);
   84         $self->writeCSV("$scoringDir/s${setID}ful.csv", @full);
   85       } else {
   86         @totalsColumn  = $self->scoreSet($setID, "totals", $showIndex, @userInfo);
   87         $self->appendColumns(\@totals, \@totalsColumn);
   88       }
   89     }
   90     $self->writeCSV("$scoringDir/${courseName}_totals.csv", @totals);
   91   }
   92 
   93   # Obtaining list of sets:
   94   #$WeBWorK::timer->continue("Begin listing sets") if defined $WeBWorK::timer;
   95   my @setNames =  $db->listGlobalSets();
   96   #$WeBWorK::timer->continue("End listing sets") if defined $WeBWorK::timer;
   97   my @set_records = ();
   98   #$WeBWorK::timer->continue("Begin obtaining sets") if defined $WeBWorK::timer;
   99   @set_records = $db->getGlobalSets( @setNames);
  100   #$WeBWorK::timer->continue("End obtaining sets: ".@set_records) if defined $WeBWorK::timer;
  101 
  102 
  103   # store data
  104   $self->{ra_sets}              =   \@setNames; # ra_sets IS NEVER USED AGAIN!!!!!
  105   $self->{ra_set_records}       =   \@set_records;
  106 }
  107 
  108 
  109 sub body {
  110   my ($self)      = @_;
  111   my $r           = $self->r;
  112   my $urlpath     = $r->urlpath;
  113   my $ce          = $r->ce;
  114   my $authz       = $r->authz;
  115   my $scoringDir  = $ce->{courseDirs}->{scoring};
  116   my $courseName  = $urlpath->arg("courseID");
  117   my $user        = $r->param('user');
  118 
  119   my $scoringPage       = $urlpath->newFromModule($urlpath->module, courseID => $courseName);
  120   my $scoringURL        = $self->systemLink($scoringPage, authen=>0);
  121 
  122   my $scoringDownloadPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::ScoringDownload",
  123                                         courseID => $courseName
  124   );
  125 
  126   # Check permissions
  127   return CGI::div({class=>"ResultsWithError"}, "You are not authorized to access the Instructor tools.")
  128     unless $authz->hasPermissions($r->param("user"), "access_instructor_tools");
  129 
  130   return CGI::div({class=>"ResultsWithError"}, "You are not authorized to score sets.")
  131     unless $authz->hasPermissions($r->param("user"), "score_sets");
  132 
  133   print join("",
  134       CGI::start_form(-method=>"POST", -action=>$scoringURL),"\n",
  135       $self->hidden_authen_fields,"\n",
  136       CGI::hidden({-name=>'scoreSelected', -value=>1}),
  137       $self->popup_set_form,
  138       CGI::br(),
  139       CGI::checkbox({ -name=>'includeIndex',
  140               -value=>1,
  141               -label=>'IncludeIndex',
  142               -checked=>1,
  143                },
  144                'Include Index'
  145       ),
  146       CGI::br(),
  147       CGI::checkbox({ -name=>'recordSingleSetScores',
  148               -value=>1,
  149               -label=>'Record Scores for Single Sets',
  150               -checked=>0,
  151               },
  152              'Record Scores for Single Sets'
  153       ),
  154       CGI::br(),
  155       CGI::input({type=>'submit',value=>'Score selected set(s)...',name=>'score-sets'}),
  156 
  157   );
  158 
  159 
  160   if ($authz->hasPermissions($user, "score_sets")) {
  161     my @selected = $r->param('selectedSet');
  162     if (@selected) {
  163       print CGI::p("All of these files will also be made available for mail merge");
  164     }
  165     foreach my $setID (@selected) {
  166 
  167       my @validFiles;
  168       foreach my $type ("scr", "ful") {
  169         my $filename = "s$setID$type.csv";
  170         my $path = "$scoringDir/$filename";
  171         push @validFiles, $filename if -f $path;
  172       }
  173       if (@validFiles) {
  174         print CGI::h2("$setID");
  175         foreach my $filename (@validFiles) {
  176           #print CGI::a({href=>"../scoringDownload/?getFile=${filename}&".$self->url_authen_args}, $filename);
  177           print CGI::a({href=>$self->systemLink($scoringDownloadPage,
  178                          params=>{getFile => $filename } )}, $filename);
  179           print CGI::br();
  180         }
  181         print CGI::hr();
  182       }
  183     }
  184     if (-f "$scoringDir/${courseName}_totals.csv") {
  185       print CGI::h2("Totals");
  186       #print CGI::a({href=>"../scoringDownload/?getFile=${courseName}_totals.csv&".$self->url_authen_args}, "${courseName}_totals.csv");
  187       print CGI::a({href=>$self->systemLink($scoringDownloadPage,
  188                          params=>{getFile => "${courseName}_totals.csv" } )}, "${courseName}_totals.csv");
  189       print CGI::hr();
  190       print CGI::pre({style=>'font-size:smaller'},WeBWorK::Utils::readFile("$scoringDir/${courseName}_totals.csv"));
  191     }
  192   }
  193 
  194   return "";
  195 }
  196 
  197 # If, some day, it becomes possible to assign a different number of problems to each student, this code
  198 # will have to be rewritten some.
  199 # $format can be any of "normal", "full", "everything", "info", or "totals".  An undefined value defaults to "normal"
  200 #   normal: student info, the status of each problem in the set, and a "totals" column
  201 #   full: student info, the status of each problem, and the number of correct and incorrect attempts
  202 #   everything: "full" plus a totals column
  203 #   info: student info columns only
  204 #   totals: total column only
  205 sub scoreSet {
  206   my ($self, $setID, $format, $showIndex, $UsersRef, $sortedUserIDsRef) = @_;
  207   my $r  = $self->r;
  208   my $db = $r->db;
  209   my @scoringData;
  210   my $scoringItems   = {    info             => 0,
  211                           successIndex     => 0,
  212                           setTotals        => 0,
  213                           problemScores    => 0,
  214                           problemAttempts  => 0,
  215                           header           => 0,
  216   };
  217   $format = "normal" unless defined $format;
  218   $format = "normal" unless $format eq "full" or $format eq "everything" or $format eq "totals" or $format eq "info";
  219   my $columnsPerProblem = ($format eq "full" or $format eq "everything") ? 3 : 1;
  220 
  221   my $setRecord = $db->getGlobalSet($setID); #checked
  222   die "global set $setID not found. " unless $setRecord;
  223   #my %users;
  224   #my %userStudentID=();
  225   #$WeBWorK::timer->continue("Begin getting users for set $setID") if defined($WeBWorK::timer);
  226   #foreach my $userID ($db->listUsers()) {
  227   # my $userRecord = $db->getUser($userID); # checked
  228   # die "user record for $userID not found" unless $userID;
  229   # # FIXME: if two users have the same student ID, the second one will
  230   # # clobber the first one. this is bad!
  231   # # The key is what we'd like to sort by.
  232   # $users{$userRecord->student_id} = $userRecord;
  233   # $userStudentID{$userID} = $userRecord->student_id;
  234   #}
  235   #$WeBWorK::timer->continue("End getting users for set $setID") if defined($WeBWorK::timer);
  236 
  237   my %Users = %$UsersRef; # user objects hashed on user ID
  238   my @sortedUserIDs = @$sortedUserIDsRef; # user IDs sorted by student ID
  239 
  240   my @problemIDs = $db->listGlobalProblems($setID);
  241 
  242   # determine what information will be returned
  243   if ($format eq 'normal') {
  244     $scoringItems  = {    info             => 1,
  245                           successIndex     => $showIndex,
  246                           setTotals        => 1,
  247                           problemScores    => 1,
  248                           problemAttempts  => 0,
  249                           header           => 1,
  250     };
  251   } elsif ($format eq 'full') {
  252     $scoringItems  = {    info             => 1,
  253                           successIndex     => $showIndex,
  254                           setTotals        => 0,
  255                           problemScores    => 1,
  256                           problemAttempts  => 1,
  257                           header           => 1,
  258     };
  259   } elsif ($format eq 'everything') {
  260     $scoringItems  = {    info             => 1,
  261                           successIndex     => $showIndex,
  262                           setTotals        => 1,
  263                           problemScores    => 1,
  264                           problemAttempts  => 1,
  265                           header           => 1,
  266     };
  267   } elsif ($format eq 'totals') {
  268     $scoringItems  = {    info             => 0,
  269                           successIndex     => $showIndex,
  270                           setTotals        => 1,
  271                           problemScores    => 0,
  272                           problemAttempts  => 0,
  273                           header           => 0,
  274     };
  275   } elsif ($format eq 'info') {
  276     $scoringItems  = {    info             => 0,
  277                           successIndex     => 0,
  278                           setTotals        => 0,
  279                           problemScores    => 0,
  280                           problemAttempts  => 0,
  281                           header           => 1,
  282     };
  283   } else {
  284     warn "unrecognized format";
  285   }
  286 
  287   # Initialize a two-dimensional array of the proper size
  288   for (my $i = 0; $i < @sortedUserIDs + 7; $i++) { # 7 is how many descriptive fields there are in each column
  289     push @scoringData, [];
  290   }
  291 
  292   my @userInfoColumnHeadings = ("STUDENT ID", "LAST NAME", "FIRST NAME", "SECTION", "RECITATION");
  293   my @userInfoFields = ("student_id", "last_name", "first_name", "section", "recitation");
  294   #my @userKeys = sort keys %users; # list of "student IDs" NOT user IDs
  295 
  296   if ($scoringItems->{header}) {
  297     $scoringData[0][0] = "NO OF FIELDS";
  298     $scoringData[1][0] = "SET NAME";
  299     $scoringData[2][0] = "PROB NUMBER";
  300     $scoringData[3][0] = "DUE DATE";
  301     $scoringData[4][0] = "DUE TIME";
  302     $scoringData[5][0] = "PROB VALUE";
  303 
  304 
  305 
  306   # Write identifying information about the users
  307 
  308     for (my $field=0; $field < @userInfoFields; $field++) {
  309       if ($field > 0) {
  310         for (my $i = 0; $i < 6; $i++) {
  311           $scoringData[$i][$field] = "";
  312         }
  313       }
  314       $scoringData[6][$field] = $userInfoColumnHeadings[$field];
  315       for (my $user = 0; $user < @sortedUserIDs; $user++) {
  316         my $fieldName = $userInfoFields[$field];
  317         $scoringData[$user + 7][$field] = $Users{$sortedUserIDs[$user]}->$fieldName;
  318       }
  319     }
  320   }
  321   return @scoringData if $format eq "info";
  322 
  323   # pre-fetch global problems
  324   $WeBWorK::timer->continue("pre-fetching global problems for set $setID") if defined($WeBWorK::timer);
  325   my %GlobalProblems = map { $_->problem_id => $_ }
  326     $db->getAllGlobalProblems($setID);
  327   $WeBWorK::timer->continue("done pre-fetching global problems for set $setID") if defined($WeBWorK::timer);
  328 
  329   # pre-fetch user problems
  330   $WeBWorK::timer->continue("pre-fetching user problems for set $setID") if defined($WeBWorK::timer);
  331   my %UserProblems; # $UserProblems{$userID}{$problemID}
  332   foreach my $userID (@sortedUserIDs) {
  333     my %CurrUserProblems = map { $_->problem_id => $_ }
  334       $db->getAllUserProblems($userID, $setID);
  335     $UserProblems{$userID} = \%CurrUserProblems;
  336   }
  337   $WeBWorK::timer->continue("done pre-fetching user problems for set $setID") if defined($WeBWorK::timer);
  338 
  339   # Write the problem data
  340   my $dueDateString = $self->formatDateTime($setRecord->due_date);
  341   my ($dueDate, $dueTime) = $dueDateString =~ m/^([^\s]*)\s*([^\s]*)$/;
  342   my $valueTotal = 0;
  343   my %userStatusTotals = ();
  344   my %userSuccessIndex = ();
  345   my %numberOfAttempts = ();
  346   my $num_of_problems  = @problemIDs;
  347   for (my $problem = 0; $problem < @problemIDs; $problem++) {
  348 
  349     #my $globalProblem = $db->getGlobalProblem($setID, $problemIDs[$problem]); #checked
  350     my $globalProblem = $GlobalProblems{$problemIDs[$problem]};
  351     die "global problem $problemIDs[$problem] not found for set $setID" unless $globalProblem;
  352 
  353     my $column = 5 + $problem * $columnsPerProblem;
  354     if ($scoringItems->{header}) {
  355       $scoringData[0][$column] = "";
  356       $scoringData[1][$column] = $setRecord->set_id;
  357       $scoringData[2][$column] = $globalProblem->problem_id;
  358       $scoringData[3][$column] = $dueDate;
  359       $scoringData[4][$column] = $dueTime;
  360       $scoringData[5][$column] = $globalProblem->value;
  361       $scoringData[6][$column] = "STATUS";
  362       if ($scoringItems->{header} and $scoringItems->{problemAttempts}) { # Fill in with blanks, or maybe the problem number
  363         for (my $row = 0; $row < 6; $row++) {
  364           for (my $col = $column+1; $col <= $column + 2; $col++) {
  365             if ($row == 2) {
  366               $scoringData[$row][$col] = $globalProblem->problem_id;
  367             } else {
  368               $scoringData[$row][$col] = "";
  369             }
  370           }
  371         }
  372         $scoringData[6][$column + 1] = "#corr";
  373         $scoringData[6][$column + 2] = "#incorr";
  374       }
  375     }
  376     $valueTotal += $globalProblem->value;
  377 
  378     #my @userLoginIDs = $db->listUsers();
  379     #$WeBWorK::timer->continue("Begin getting user problems for set $setID, problem $problemIDs[$problem]") if defined($WeBWorK::timer);
  380     ##my @userProblems = $db->getMergedProblems( map { [ $_, $setID, $problemIDs[$problem] ] } @userLoginIDs );
  381     #my @userProblems = $db->getUserProblems( map { [ $_, $setID, $problemIDs[$problem] ] }    @userLoginIDs ); # checked
  382     #my %userProblems;
  383     #foreach my $item (@userProblems) {
  384     # $userProblems{$item->user_id} = $item if ref $item;
  385     #}
  386     #$WeBWorK::timer->continue("End getting user problems for set $setID, problem $problemIDs[$problem]") if defined($WeBWorK::timer);
  387 
  388     for (my $user = 0; $user < @sortedUserIDs; $user++) {
  389       #my $userProblem = $userProblems{    $users{$userKeys[$user]}->user_id   };
  390       #my $userProblem = $UserProblems{$sers{$userKeys[$user]}->user_id}{$problemIDs[$problem]};
  391       my $userProblem = $UserProblems{$sortedUserIDs[$user]}{$problemIDs[$problem]};
  392       unless (defined $userProblem) { # assume an empty problem record if the problem isn't assigned to this user
  393         $userProblem = $db->newUserProblem;
  394         $userProblem->status(0);
  395         $userProblem->value(0);
  396         $userProblem->num_correct(0);
  397         $userProblem->num_incorrect(0);
  398       }
  399       $userStatusTotals{$user} = 0 unless exists $userStatusTotals{$user};
  400       #$userStatusTotals{$user} += $userProblem->status * $userProblem->value;
  401       $userStatusTotals{$user} += $userProblem->status * $globalProblem->value;
  402       if ($scoringItems->{successIndex})   {
  403         $numberOfAttempts{$user}  = 0 unless defined($numberOfAttempts{$user});
  404         my $num_correct     = $userProblem->num_correct;
  405         my $num_incorrect   = $userProblem->num_incorrect;
  406         $num_correct        = ( defined($num_correct) and $num_correct) ? $num_correct : 0;
  407         $num_incorrect      = ( defined($num_incorrect) and $num_incorrect) ? $num_incorrect : 0;
  408         $numberOfAttempts{$user} += $num_correct + $num_incorrect;
  409       }
  410       if ($scoringItems->{problemScores}) {
  411         $scoringData[7 + $user][$column] = $userProblem->status;
  412         if ($scoringItems->{problemAttempts}) {
  413           $scoringData[7 + $user][$column + 1] = $userProblem->num_correct;
  414           $scoringData[7 + $user][$column + 2] = $userProblem->num_incorrect;
  415         }
  416       }
  417     }
  418   }
  419   if ($scoringItems->{successIndex}) {
  420     for (my $user = 0; $user < @sortedUserIDs; $user++) {
  421       my $avg_num_attempts = ($num_of_problems) ? $numberOfAttempts{$user}/$num_of_problems : 0;
  422       $userSuccessIndex{$user} = ($avg_num_attempts) ? ($userStatusTotals{$user}/$valueTotal)**2/$avg_num_attempts : 0;
  423     }
  424   }
  425   # write the status totals
  426   if ($scoringItems->{setTotals}) { # Ironic, isn't it?
  427     my $totalsColumn = $format eq "totals" ? 0 : 5 + @problemIDs * $columnsPerProblem;
  428     $scoringData[0][$totalsColumn]    = "";
  429     $scoringData[1][$totalsColumn]    = $setRecord->set_id;
  430     $scoringData[1][$totalsColumn+1]  = $setRecord->set_id if $scoringItems->{successIndex};
  431     $scoringData[2][$totalsColumn]    = "";
  432     $scoringData[3][$totalsColumn]    = "";
  433     $scoringData[4][$totalsColumn]    = "";
  434     $scoringData[5][$totalsColumn]    = $valueTotal;
  435     $scoringData[6][$totalsColumn]    = "total";
  436     $scoringData[6][$totalsColumn+1]  = "index" if $scoringItems->{successIndex};
  437     for (my $user = 0; $user < @sortedUserIDs; $user++) {
  438       $scoringData[7+$user][$totalsColumn] = sprintf("%4.1f",$userStatusTotals{$user});
  439       $scoringData[7+$user][$totalsColumn+1] = sprintf("%4.1f",$userSuccessIndex{$user}) if $scoringItems->{successIndex};
  440     }
  441   }
  442   $WeBWorK::timer->continue("End  set $setID") if defined($WeBWorK::timer);
  443   return @scoringData;
  444 }
  445 
  446 # Often it's more efficient to just get everything out of the database
  447 # and then pick out what you want later.  Hence, these "everything2*" functions
  448 sub everything2info {
  449   my ($self, @everything) = @_;
  450   my @result = ();
  451   foreach my $row (@everything) {
  452     push @result, [@{$row}[0..4]];
  453   }
  454   return @result;
  455 }
  456 
  457 sub everything2normal {
  458   my ($self, @everything) = @_;
  459   my @result = ();
  460   foreach my $row (@everything) {
  461     my @row = @$row;
  462     my @newRow = ();
  463     push @newRow, @row[0..4];
  464     for (my $i = 5; $i < @row; $i+=3) {
  465       push @newRow, $row[$i];
  466     }
  467     #push @newRow, $row[$#row];
  468     push @result, [@newRow];
  469   }
  470   return @result;
  471 }
  472 
  473 sub everything2full {
  474   my ($self, @everything) = @_;
  475   my @result = ();
  476   foreach my $row (@everything) {
  477     push @result, [@{$row}[0..($#{$row}-1)]];
  478   }
  479   return @result;
  480 }
  481 
  482 sub everything2totals {
  483   my ($self, @everything) = @_;
  484   my @result = ();
  485   foreach my $row (@everything) {
  486     push @result, [${$row}[$#{$row}]];
  487   }
  488   return @result;
  489 }
  490 
  491 sub appendColumns {
  492   my ($self, $a1, $a2) = @_;
  493   my @a1 = @$a1;
  494   my @a2 = @$a2;
  495   for (my $i = 0; $i < @a1; $i++) {
  496     push @{$a1[$i]}, @{$a2[$i]};
  497   }
  498 }
  499 
  500 # Reads a CSV file and returns an array of arrayrefs, each containing a
  501 # row of data:
  502 # (["c1r1", "c1r2", "c1r3"], ["c2r1", "c2r2", "c2r3"])
  503 sub readCSV {
  504   my ($self, $fileName) = @_;
  505   my @result = ();
  506   my @rows = split m/\n/, readFile($fileName);
  507   foreach my $row (@rows) {
  508     push @result, [split m/\s*,\s*/, $row];
  509   }
  510   return @result;
  511 }
  512 
  513 # Write a CSV file from an array in the same format that readCSV produces
  514 sub writeCSV {
  515   my ($self, $filename, @csv) = @_;
  516 
  517   my @lengths = ();
  518   for (my $row = 0; $row < @csv; $row++) {
  519     for (my $column = 0; $column < @{$csv[$row]}; $column++) {
  520       $lengths[$column] = 0 unless defined $lengths[$column];
  521       $lengths[$column] = length $csv[$row][$column] if defined($csv[$row][$column]) and length $csv[$row][$column] > $lengths[$column];
  522     }
  523   }
  524 
  525   open my $fh, ">", $filename or warn "Unable to open $filename for writing";
  526   foreach my $row (@csv) {
  527     my @rowPadded = ();
  528     foreach (my $column = 0; $column < @$row; $column++) {
  529       push @rowPadded, $self->pad($row->[$column], $lengths[$column] + 1);
  530     }
  531     print $fh join(",", @rowPadded);
  532     print $fh "\n";
  533   }
  534   close $fh;
  535 }
  536 
  537 # As soon as backwards compatability is no longer a concern and we don't expect to have
  538 # to use old ww1.x code to read the output anymore, I recommend switching to using
  539 # these routines, which are more versatile and compatable with other programs which
  540 # deal with CSV files.
  541 sub readStandardCSV {
  542   my ($self, $fileName) = @_;
  543   my @result = ();
  544   my @rows = split m/\n/, readFile($fileName);
  545   foreach my $row (@rows) {
  546     push @result, [$self->splitQuoted($row)];
  547   }
  548   return @result;
  549 }
  550 
  551 sub writeStandardCSV {
  552   my ($self, $filename, @csv) = @_;
  553   open my $fh, ">", $filename;
  554   foreach my $row (@csv) {
  555     print $fh (join ",", map {$self->quote($_)} @$row);
  556     print $fh "\n";
  557   }
  558   close $fh;
  559 }
  560 
  561 ###
  562 
  563 # This particular unquote method unquotes (optionally) quoted strings in the
  564 # traditional CSV style (double-quote for literal quote, etc.)
  565 sub unquote {
  566   my ($self, $string) = @_;
  567   if ($string =~ m/^"(.*)"$/) {
  568     $string = $1;
  569     $string =~ s/""/"/;
  570   }
  571   return $string;
  572 }
  573 
  574 # Should you wish to treat whitespace differently, this routine has been designed
  575 # to make it easy to do so.
  576 sub splitQuoted {
  577   my ($self, $string) = @_;
  578   my ($leadingSpace, $preText, $quoted, $postText, $trailingSpace, $result);
  579   my @result = ();
  580   my $continue = 1;
  581   while ($continue) {
  582     $string =~ m/\G(\s*)/gc;
  583     $leadingSpace = $1;
  584     $string =~ m/\G([^",]*)/gc;
  585     $preText = $1;
  586     if ($string =~ m/\G"((?:[^"]|"")*)"/gc) {
  587       $quoted = $1;
  588     }
  589     $string =~ m/\G([^,]*?)(\s*)(,?)/gc;
  590     ($postText, $trailingSpace, $continue) = ($1, $2, $3);
  591 
  592     $preText = "" unless defined $preText;
  593     $postText = "" unless defined $postText;
  594     $quoted = "" unless defined $quoted;
  595 
  596     if ($quoted and (not $preText and not $postText)) {
  597         $quoted =~ s/""/"/;
  598         $result = $quoted;
  599     } else {
  600       $result = "$preText$quoted$postText";
  601     }
  602     push @result, $result;
  603   }
  604   return @result;
  605 }
  606 
  607 # This particular quoting method does CSV-style (double a quote to escape it) quoting when necessary.
  608 sub quote {
  609   my ($self, $string) = @_;
  610   if ($string =~ m/[", ]/) {
  611     $string =~ s/"/""/;
  612     $string = "\"$string\"";
  613   }
  614   return $string;
  615 }
  616 
  617 sub pad {
  618   my ($self, $string, $padTo) = @_;
  619   $string = '' unless defined $string;
  620   my $spaces = $padTo - length $string;
  621   return $string . " "x$spaces;
  622 }
  623 
  624 sub maxLength {
  625   my ($self, $arrayRef) = @_;
  626   my $max = 0;
  627   foreach my $cell (@$arrayRef) {
  628     $max = length $cell unless length $cell < $max;
  629   }
  630   return $max;
  631 }
  632 
  633 sub popup_set_form {
  634   my $self  = shift;
  635   my $r     = $self->r;
  636   my $db    = $r->db;
  637   my $ce    = $r->ce;
  638   my $authz = $r->authz;
  639   my $user  = $r->param('user');
  640 
  641   my $root = $ce->{webworkURLs}->{root};
  642   my $courseName = $ce->{courseName};
  643 
  644  #     return CGI::em("You are not authorized to access the Instructor tools.") unless $authz->hasPermissions($user, "access_instructor_tools");
  645 
  646   # This code will require changing if the permission and user tables ever have different keys.
  647     my @setNames              = ();
  648   my $ra_set_records        = $self->{ra_set_records};
  649   my %setLabels             = ();#  %$hr_classlistLabels;
  650   my @set_records           =  sort {$a->set_id cmp $b->set_id } @{$ra_set_records};
  651   foreach my $sr (@set_records) {
  652     $setLabels{$sr->set_id} = $sr->set_id;
  653     push(@setNames, $sr->set_id);  # reorder sets
  654   }
  655   return      CGI::popup_menu(-name=>'selectedSet',
  656                  -values=>\@setNames,
  657                  -labels=>\%setLabels,
  658                  -size  => 10,
  659                  -multiple => 1,
  660                  #-default=>$user
  661           ),
  662 
  663 
  664 }
  665 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9