[LON-CAPA-cvs] cvs: loncom /homework grades.pm /interface domainprefs.pm lonconfigsettings.pm /lonnet/perl lonnet.pm

raeburn raeburn at source.lon-capa.org
Sun Jan 27 09:40:04 EST 2019


raeburn		Sun Jan 27 14:40:04 2019 EDT

  Modified files:              
    /loncom/interface	domainprefs.pm lonconfigsettings.pm 
    /loncom/homework	grades.pm 
    /loncom/lonnet/perl	lonnet.pm 
  Log:
  - Uploaded bubblesheet data can consist of comma separated values.
    - Data will be converted to one of formats specified in bubblesheet
      format file, prior to saving in scantron_orig_* file. 
    - Domain configuration for bubblesheet data format allows order of
       columns in csv data to be specified.
    - &get_scantronformat_file and &get_scantron_config moved from grades.pm
      to lonnet.pm to facilitate re-use.
  
  
-------------- next part --------------
Index: loncom/interface/domainprefs.pm
diff -u loncom/interface/domainprefs.pm:1.345 loncom/interface/domainprefs.pm:1.346
--- loncom/interface/domainprefs.pm:1.345	Wed Dec 26 20:10:24 2018
+++ loncom/interface/domainprefs.pm	Sun Jan 27 14:39:48 2019
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Handler to set domain-wide configuration settings
 #
-# $Id: domainprefs.pm,v 1.345 2018/12/26 20:10:24 raeburn Exp $
+# $Id: domainprefs.pm,v 1.346 2019/01/27 14:39:48 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -394,11 +394,12 @@
                     modify => \&modify_usermodification,
                   },
         'scantron' =>
-                  { text => 'Bubblesheet format file',
+                  { text => 'Bubblesheet format',
                     help => 'Domain_Configuration_Scantron_Format',
-                    header => [ {col1 => 'Item',
-                                 col2 => '',
-                              }],
+                    header => [ {col1 => 'Bubblesheet format file',
+                                 col2 => ''},
+                                {col1 => 'Bubblesheet data upload formats',
+                                 col2 => 'Settings'}],
                     print => \&print_scantron,
                     modify => \&modify_scantron,
                   },
@@ -627,6 +628,9 @@
         if (grep(/^contacts$/, at actions)) {
             $js .= &contacts_javascript();
         }
+        if (grep(/^scantron$/, at actions)) {
+            $js .= &scantron_javascript();
+        }
         &Apache::lonconfigsettings::display_settings($r,$dom,$phase,$context,\@prefs_order,\%prefs,\%domconfig,$confname,$js);
     } else {
 # check if domconfig user exists for the domain.
@@ -828,6 +832,8 @@
             $output .= $item->{'print'}->('top',$dom,$settings,\$rowtotal);
         } elsif ($action eq 'coursecategories') {
             $output .= $item->{'print'}->('top',$dom,$item,$settings,\$rowtotal);
+        } elsif ($action eq 'scantron') {
+            $output .= $item->{'print'}->($r,'top',$dom,$confname,$settings,\$rowtotal);
         } elsif ($action eq 'login') {
             if ($numheaders == 4) {
                 $colspan = ' colspan="2"';
@@ -932,6 +938,8 @@
                  ($action eq 'defaults') || ($action eq 'directorysrch') ||
                  ($action eq 'helpsettings')) {
             $output .= $item->{'print'}->('bottom',$dom,$settings,\$rowtotal);
+        } elsif ($action eq 'scantron') {
+            $output .= $item->{'print'}->($r,'bottom',$dom,$confname,$settings,\$rowtotal);
         } elsif ($action eq 'ssl') {
             $output .= $item->{'print'}->('connto',$dom,$settings,\$rowtotal).'
             </table>
@@ -1113,8 +1121,6 @@
                  ($action eq 'serverstatuses') || ($action eq 'loadbalancing') || 
                  ($action eq 'ltitools') || ($action eq 'lti')) {
             $output .= $item->{'print'}->($dom,$settings,\$rowtotal);
-        } elsif ($action eq 'scantron') {
-            $output .= &print_scantronformat($r,$dom,$confname,$settings,\$rowtotal);
         }
     }
     $output .= '
@@ -7974,6 +7980,48 @@
     return (\%titles);
 }
 
+sub print_scantron {
+    my ($r,$position,$dom,$confname,$settings,$rowtotal) = @_;
+    if ($position eq 'top') {
+        return &print_scantronformat($r,$dom,$confname,$settings,\$rowtotal);
+    } else {
+        return &print_scantronconfig($dom,$settings,\$rowtotal);
+    }
+}
+
+sub scantron_javascript {
+    return <<"ENDSCRIPT";
+
+<script type="text/javascript">
+// <![CDATA[
+
+function toggleScantron(form) {
+    if (document.getElementById('scantroncsv_cols')) {
+        var csvfieldset = document.getElementById('scantroncsv_cols');
+        if (document.getElementById('scantronconfcsv')) {
+            var scantroncsv = document.getElementById('scantronconfcsv'); 
+            if (scantroncsv.checked) {
+                csvfieldset.style.display = 'inline-block';
+            } else {
+                csvfieldset.style.display = 'none';
+                var csvselects = document.getElementsByClassName('scantronconfig_csv');
+                if (csvselects.length) {
+                    for (var j=0; j<csvselects.length; j++) {
+                        csvselects[j].selectedIndex = 0;
+                    }
+                }
+            }
+        }
+    }
+    return;
+}
+// ]]>
+</script>
+
+ENDSCRIPT
+
+}
+
 sub print_scantronformat {
     my ($r,$dom,$confname,$settings,$rowtotal) = @_;
     my $itemcount = 1;
@@ -8000,8 +8048,8 @@
             if ($configuserok eq 'ok') {
                 if ($author_ok eq 'ok') {
                     my %legacyfile = (
- default => $Apache::lonnet::perlvar{'lonTabDir'}.'/default_scantronformat.tab', 
- custom  => $Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab', 
+ default => $Apache::lonnet::perlvar{'lonTabDir'}.'/default_scantronformat.tab',
+ custom  => $Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab',
                     );
                     my %md5chk;
                     foreach my $type (keys(%legacyfile)) {
@@ -8010,7 +8058,7 @@
                     }
                     if ($md5chk{'default'} ne $md5chk{'custom'}) {
                         foreach my $type (keys(%legacyfile)) {
-                            ($scantronurls{$type},my $error) = 
+                            ($scantronurls{$type},my $error) =
                                 &legacy_scantronformat($r,$dom,$confname,
                                                  $type,$legacyfile{$type},
                                                  $scantronurls{$type},
@@ -8021,13 +8069,13 @@
                         }
                         if (keys(%error) == 0) {
                             $is_custom = 1;
-                            $confhash{'scantron'}{'scantronformat'} = 
+                            $confhash{'scantron'}{'scantronformat'} =
                                 $scantronurls{'custom'};
-                            my $putresult = 
+                            my $putresult =
                                 &Apache::lonnet::put_dom('configuration',
                                                          \%confhash,$dom);
                             if ($putresult ne 'ok') {
-                                $error{'custom'} = 
+                                $error{'custom'} =
                                     '<span class="LC_error">'.
                                     &mt('An error occurred updating the domain configuration: [_1]',$putresult).'</span>';
                             }
@@ -8147,6 +8195,96 @@
     return ($url,$error);
 }
 
+sub print_scantronconfig {
+    my ($dom,$settings,$rowtotal) = @_;
+    my $itemcount = 2;
+    my $is_checked = ' checked="checked"';
+    my $currcsvsty = 'none';
+    my ($datatable,%csvfields,%checked,%onclick);
+    my @fields = &scantroncsv_fields();
+    my %titles = &scantronconfig_titles();
+    if (ref($settings) eq 'HASH') {
+        if (ref($settings->{config}) eq 'HASH') {
+            if ($settings->{config}->{dat}) {
+                $checked{'dat'} = $is_checked;
+            }
+            if (ref($settings->{config}->{csv}) eq 'HASH') {
+                %csvfields = %{$settings->{config}->{csv}};
+                if (keys(%csvfields) > 0) {
+                    $checked{'csv'} = $is_checked;
+                    $currcsvsty = 'inline-block';
+                }
+            }
+        } else {
+            $checked{'dat'} = $is_checked;
+        }
+    } else {
+        $checked{'dat'} = $is_checked;
+    }
+    $onclick{'csv'} = ' onclick="toggleScantron(this.form);"';
+    my $css_class = $itemcount%2? ' class="LC_odd_row"':'';
+    $datatable = '<tr '.$css_class.'><td>'.&mt('Supported formats').'</td>'.
+                 '<td class="LC_left_item" valign="top"><span class="LC_nobreak">';
+    foreach my $item ('dat','csv') {
+        my $id;
+        if ($item eq 'csv') {
+            $id = 'id="scantronconfcsv" ';
+        } 
+        $datatable .= '<label><input type="checkbox" name="scantronconfig" '.$id.'value="'.$item.'"'.$checked{$item}.$onclick{$item}.' />'.
+                      $titles{$item}.'</label>'.(' 'x3);
+        if ($item eq 'csv') {
+            $datatable .= '<fieldset style="display:'.$currcsvsty.'" id="scantroncsv_cols">'.
+                          '<legend>'.&mt('CSV Column Mapping').'</legend>'.
+                          '<table><tr><th>'.&mt('Field').'</th><th>'.&mt('Location').'</th></tr>'."\n";
+            foreach my $col (@fields) {
+                my $selnone;
+                if ($csvfields{$col} eq '') {
+                    $selnone = ' selected="selected"';
+                }
+                $datatable .= '<tr><td>'.$titles{$col}.'</td>'.
+                              '<td><select name="scantronconfig_csv_'.$col.'" class="scantronconfig_csv">'.
+                              '<option value=""'.$selnone.'></option>';
+                for (my $i=0; $i<20; $i++) {
+                    my $shown = $i+1;
+                    my $sel;
+                    unless ($selnone) {
+                        if (exists($csvfields{$col})) {
+                            if ($csvfields{$col} == $i) {
+                                $sel = ' selected="selected"';
+                            }
+                        }
+                    }
+                    $datatable .= '<option value="'.$i.'"'.$sel.'>'.$shown.'</option>';
+                }
+                $datatable .= '</select></td></tr>';
+           }
+           $datatable .= '</table></fieldset>';
+           $itemcount ++;
+        }
+    }
+    $datatable .= '</td></tr>';
+    $$rowtotal ++;
+    return $datatable;
+}
+
+sub scantronconfig_titles {
+    return &Apache::lonlocal::texthash(
+                                          dat => 'Standard format (.dat)',
+                                          csv => 'Comma separated values (.csv)',
+                                          CODE => 'CODE',
+                                          ID   => 'Student ID',
+                                          PaperID => 'Paper ID',
+                                          FirstName => 'First Name',
+                                          LastName => 'Last Name',
+                                          FirstQuestion => 'First Question Response',
+                                          Section => 'Section',
+    );
+}
+
+sub scantroncsv_fields {
+    return ('PaperID','LastName','FirstName','ID','Section','CODE','FirstQuestion');
+}
+
 sub print_coursecategories {
     my ($position,$dom,$hdritem,$settings,$rowtotal) = @_;
     my $datatable;
@@ -14964,7 +15102,7 @@
     my $custom = 'custom.tab';
     my $default = 'default.tab';
     my $servadm = $r->dir_config('lonAdmEMail');
-    my ($configuserok,$author_ok,$switchserver) = 
+    my ($configuserok,$author_ok,$switchserver) =
         &config_check($dom,$confname,$servadm);
     if ($env{'form.scantronformat.filename'} ne '') {
         my $error;
@@ -15002,6 +15140,56 @@
             }
         }
     }
+    my @fields = &scantroncsv_fields();
+    my %titles = &scantronconfig_titles();
+    my @formats = &Apache::loncommon::get_env_multiple('form.scantronformat');
+    my ($newdat,$currdat,%newcol,%currcol);
+    if (grep(/^dat$/, at formats)) {
+        $confhash{'scantron'}{config}{dat} = 1;
+        $newdat = 1;
+    } else {
+        $confhash{'scantron'}{config}{dat} = 0;
+        $newdat = 0;
+    }
+    if (grep(/^csv$/, at formats)) {
+        my %bynum;
+        foreach my $field (@fields) {
+            if ($env{'form.scantronconfig_csv_'.$field} =~ /^(\d+)$/) {
+                my $posscol = $1;
+                if (($posscol < 20) && (!$bynum{$posscol})) {
+                    $confhash{'scantron'}{config}{csv}{$field} = $posscol;
+                    $bynum{$posscol} = $field;
+                    $newcol{$field} = $posscol;
+                }
+            }
+        }
+    }
+    $currdat = 1;
+    if (ref($domconfig{'scantron'}) eq 'HASH') {
+        if (ref($domconfig{'scantron'}{'config'}) eq 'HASH') {
+            if (!$domconfig{'scantron'}{'config'}{'dat'}) {
+                $currdat = 0;
+            }
+            if (ref($domconfig{'scantron'}{'config'}{'csv'}) eq 'HASH') {
+                %currcol = %{$domconfig{'scantron'}{'config'}{'csv'}};
+            }
+        }
+    }
+    if ($currdat != $newdat) {
+        $changes{'config'} = 1;
+    } else {
+        foreach my $field (@fields) {
+            if ($currcol{$field} ne '') {
+                if ($currcol{$field} ne $newcol{$field}) {
+                    $changes{'config'} = 1;
+                    last;
+                } 
+            } elsif ($newcol{$field} ne '') {
+                $changes{'config'} = 1;
+                last;
+            }
+        }
+    }
     if (keys(%confhash) > 0) {
         my $putresult = &Apache::lonnet::put_dom('configuration',\%confhash,
                                                  $dom);
@@ -15009,22 +15197,44 @@
             if (keys(%changes) > 0) {
                 if (ref($confhash{'scantron'}) eq 'HASH') {
                     $resulttext = &mt('Changes made:').'<ul>';
-                    if ($confhash{'scantron'}{'scantronformat'} eq '') {
-                        $resulttext .= '<li>'.&mt('[_1] bubblesheet format file removed; [_2] file will be used for courses in this domain.',$custom,$default).'</li>';
-                    } else {
-                        $resulttext .= '<li>'.&mt('Custom bubblesheet format file ([_1]) uploaded for use with courses in this domain.',$custom).'</li>';
+                    if ($changes{'scantronformat'}) {
+                        if ($confhash{'scantron'}{'scantronformat'} eq '') {
+                            $resulttext .= '<li>'.&mt('[_1] bubblesheet format file removed; [_2] file will be used for courses in this domain.',$custom,$default).'</li>';
+                        } else {
+                            $resulttext .= '<li>'.&mt('Custom bubblesheet format file ([_1]) uploaded for use with courses in this domain.',$custom).'</li>';
+                        }
+                    }
+                    if ($changes{'config'}) {    
+                        if (ref($confhash{'scantron'}{'config'}) eq 'HASH') {
+                            if ($confhash{'scantron'}{'config'}{'dat'}) {
+                                $resulttext .= '<li>'.&mt('Bubblesheet data upload formats includes .dat format').'</li>';
+                            }
+                            if (ref($confhash{'scantron'}{'config'}{'csv'}) eq 'HASH') {
+                                if (keys(%{$confhash{'scantron'}{'config'}{'csv'}})) { 
+                                    '<li>'.&mt('Bubblesheet data upload formats includes .csv format, with following fields/column numbers supported:').'<ul>';
+                                    foreach my $field (@fields) {
+                                        if ($confhash{'scantron'}{'config'}{'csv'}{$field} ne '') {
+                                            my $showcol = $confhash{'scantron'}{'config'}{'csv'}{$field} + 1; 
+                                            $resulttext .= '<li>'.$titles{$field}.': '.$showcol.'</li>';
+                                        }
+                                    }
+                                    $resulttext .= '</ul></li>';
+                                }
+                            }
+                        } else {
+                            $resulttext .= '<li>'.&mt('No bubblesheet data upload formats set -- will default to assuming .dat format').'</li>';
+                        }
                     }
                     $resulttext .= '</ul>';
                 } else {
                     $resulttext = &mt('Changes made to bubblesheet format file.');
                 }
-                $resulttext .= '</ul>';
                 &Apache::loncommon::devalidate_domconfig_cache($dom);
                 if (ref($lastactref) eq 'HASH') {
                     $lastactref->{'domainconfig'} = 1;
                 }
             } else {
-                $resulttext = &mt('No changes made to bubblesheet format file');
+                $resulttext = &mt('No changes made to bubblesheet format settings');
             }
         } else {
             $resulttext = '<span class="LC_error">'.
Index: loncom/interface/lonconfigsettings.pm
diff -u loncom/interface/lonconfigsettings.pm:1.44 loncom/interface/lonconfigsettings.pm:1.45
--- loncom/interface/lonconfigsettings.pm:1.44	Tue Aug 14 21:42:40 2018
+++ loncom/interface/lonconfigsettings.pm	Sun Jan 27 14:39:48 2019
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Handler to set domain-wide configuration settings
 #
-# $Id: lonconfigsettings.pm,v 1.44 2018/08/14 21:42:40 raeburn Exp $
+# $Id: lonconfigsettings.pm,v 1.45 2019/01/27 14:39:48 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -251,6 +251,9 @@
                     }
                 }
             }
+            if (grep(/^scantron$/, at actions)) {
+                $onload .= "toggleScantron('document.display');";
+            }
             if ($onload) {
                 my %loaditems = (
                                   'onload' => $onload,
Index: loncom/homework/grades.pm
diff -u loncom/homework/grades.pm:1.753 loncom/homework/grades.pm:1.754
--- loncom/homework/grades.pm:1.753	Tue Nov 20 19:14:14 2018
+++ loncom/homework/grades.pm	Sun Jan 27 14:39:55 2019
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # The LON-CAPA Grading handler
 #
-# $Id: grades.pm,v 1.753 2018/11/20 19:14:14 raeburn Exp $
+# $Id: grades.pm,v 1.754 2019/01/27 14:39:55 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -5805,7 +5805,7 @@
 sub scantron_scantab {
     my $result='<select name="scantron_format">'."\n";
     $result.='<option></option>'."\n";
-    my @lines = &get_scantronformat_file();
+    my @lines = &Apache::lonnet::get_scantronformat_file();
     if (@lines > 0) {
         foreach my $line (@lines) {
             next if (($line =~ /^\#/) || ($line eq ''));
@@ -5817,62 +5817,6 @@
     return $result;
 }
 
-=pod
-
-=item get_scantronformat_file
-
-  Returns an array containing lines from the scantron format file for
-  the domain of the course.
-
-  If a url for a custom.tab file is listed in domain's configuration.db, 
-  lines are from this file.
-
-  Otherwise, if a default.tab has been published in RES space by the 
-  domainconfig user, lines are from this file.
-
-  Otherwise, fall back to getting lines from the legacy file on the
-  local server:  /home/httpd/lonTabs/default_scantronformat.tab    
-
-=cut
-
-sub get_scantronformat_file {
-    my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};
-    my %domconfig = &Apache::lonnet::get_dom('configuration',['scantron'],$cdom);
-    my $gottab = 0;
-    my @lines;
-    if (ref($domconfig{'scantron'}) eq 'HASH') {
-        if ($domconfig{'scantron'}{'scantronformat'} ne '') {
-            my $formatfile = &Apache::lonnet::getfile($Apache::lonnet::perlvar{'lonDocRoot'}.$domconfig{'scantron'}{'scantronformat'});
-            if ($formatfile ne '-1') {
-                @lines = split("\n",$formatfile,-1);
-                $gottab = 1;
-            }
-        }
-    }
-    if (!$gottab) {
-        my $confname = $cdom.'-domainconfig';
-        my $default = $Apache::lonnet::perlvar{'lonDocRoot'}.'/res/'.$cdom.'/'.$confname.'/default.tab';
-        my $formatfile =  &Apache::lonnet::getfile($default);
-        if ($formatfile ne '-1') {
-            @lines = split("\n",$formatfile,-1);
-            $gottab = 1;
-        }
-    }
-    if (!$gottab) {
-        my @domains = &Apache::lonnet::current_machine_domains();
-        if (grep(/^\Q$cdom\E$/, at domains)) {
-            my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
-            @lines = <$fh>;
-            close($fh);
-        } else {
-            my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/default_scantronformat.tab');
-            @lines = <$fh>;
-            close($fh);
-        }
-    }
-    return @lines;
-}
-
 =pod 
 
 =item scantron_CODElist
@@ -5957,21 +5901,13 @@
 	# Chunk of form to prompt for a scantron file upload.
 
         $r->print('
-    <br />
-    '.&Apache::loncommon::start_data_table('LC_scantron_action').'
-       '.&Apache::loncommon::start_data_table_header_row().'
-            <th>
-               '.&mt('Specify a bubblesheet data file to upload.').'
-            </th>
-       '.&Apache::loncommon::end_data_table_header_row().'
-       '.&Apache::loncommon::start_data_table_row().'
-            <td>
-');
+    <br />');
     my $default_form_data=&defaultFormData($symb);
     my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};
     my $cnum= $env{'course.'.$env{'request.course.id'}.'.num'};
     my $alertmsg = &mt('Please use the browse button to select a file from your local directory.');
     &js_escape(\$alertmsg);
+    my ($formatoptions,$formattitle,$formatjs) = &scantron_upload_dataformat($cdom);
     $r->print(&Apache::lonhtmlcommon::scripttag('
     function checkUpload(formname) {
 	if (formname.upfile.value == "") {
@@ -5979,24 +5915,42 @@
 	    return false;
 	}
 	formname.submit();
-    }'));
+    }'.$formatjs));
     $r->print('
               <form enctype="multipart/form-data" action="/adm/grades" name="rules" method="post">
                 '.$default_form_data.'
                 <input name="courseid" type="hidden" value="'.$cnum.'" />
                 <input name="domainid" type="hidden" value="'.$cdom.'" />
                 <input name="command" value="scantronupload_save" type="hidden" />
-                '.&mt('File to upload: [_1]','<input type="file" name="upfile" size="50" />').'
-                <br />
-                <input type="button" onclick="javascript:checkUpload(this.form);" value="'.&mt('Upload Bubblesheet Data').'" />
-              </form>
-');
+              '.&Apache::loncommon::start_data_table('LC_scantron_action').'
+              '.&Apache::loncommon::start_data_table_header_row().'
+                <th>
+                 '.&mt('Specify a bubblesheet data file to upload.').'
+                </th>
+              '.&Apache::loncommon::end_data_table_header_row().'
+              '.&Apache::loncommon::start_data_table_row().'
+            <td>
+                '.&mt('File to upload: [_1]','<input type="file" name="upfile" size="50" />').'<br />'."\n");
+    if ($formatoptions) {
+        $r->print('</td>
+                 '.&Apache::loncommon::end_data_table_row().'
+                 '.&Apache::loncommon::start_data_table_row().'
+                 <td>'.$formattitle.(' 'x2).$formatoptions.'
+                 </td>
+                 '.&Apache::loncommon::end_data_table_row().'
+                 '.&Apache::loncommon::start_data_table_row().'
+                 <td>'
+        );
+    } else {
+        $r->print(' <br />');
+    }
+    $r->print('<input type="button" onclick="javascript:checkUpload(this.form);" value="'.&mt('Upload Bubblesheet Data').'" />
+              </td>
+             '.&Apache::loncommon::end_data_table_row().'
+             '.&Apache::loncommon::end_data_table().'
+             </form>'
+    );
 
-        $r->print('
-            </td>
-       '.&Apache::loncommon::end_data_table_row().'
-       '.&Apache::loncommon::end_data_table().'
-');
     }
 
     # Chunk of form to prompt for a file to grade and how:
@@ -6109,98 +6063,6 @@
     return;
 }
 
-=pod
-
-=item get_scantron_config
-
-   Parse and return the bubblesheet configuration line selected as a
-   hash of configuration file fields.
-
- Arguments:
-    which - the name of the configuration to parse from the file.
-
-
- Returns:
-            If the named configuration is not in the file, an empty
-            hash is returned.
-    a hash with the fields
-      name         - internal name for the this configuration setup
-      description  - text to display to operator that describes this config
-      CODElocation - if 0 or the string 'none'
-                          - no CODE exists for this config
-                     if -1 || the string 'letter'
-                          - a CODE exists for this config and is
-                            a string of letters
-                     Unsupported value (but planned for future support)
-                          if a positive integer
-                               - The CODE exists as the first n items from
-                                 the question section of the form
-                          if the string 'number'
-                               - The CODE exists for this config and is
-                                 a string of numbers
-      CODEstart   - (only matter if a CODE exists) column in the line where
-                     the CODE starts
-      CODElength  - length of the CODE
-      IDstart     - column where the student/employee ID starts
-      IDlength    - length of the student/employee ID info
-      Qstart      - column where the information from the bubbled
-                    'questions' start
-      Qlength     - number of columns comprising a single bubble line from
-                    the sheet. (usually either 1 or 10)
-      Qon         - either a single character representing the character used
-                    to signal a bubble was chosen in the positional setup, or
-                    the string 'letter' if the letter of the chosen bubble is
-                    in the final, or 'number' if a number representing the
-                    chosen bubble is in the file (1->A 0->J)
-      Qoff        - the character used to represent that a bubble was
-                    left blank
-      PaperID     - if the scanning process generates a unique number for each
-                    sheet scanned the column that this ID number starts in
-      PaperIDlength - number of columns that comprise the unique ID number
-                      for the sheet of paper
-      FirstName   - column that the first name starts in
-      FirstNameLength - number of columns that the first name spans
- 
-      LastName    - column that the last name starts in
-      LastNameLength - number of columns that the last name spans
-      BubblesPerRow - number of bubbles available in each row used to 
-                      bubble an answer. (If not specified, 10 assumed).
-
-=cut
-
-sub get_scantron_config {
-    my ($which) = @_;
-    my @lines = &get_scantronformat_file();
-    my %config;
-    #FIXME probably should move to XML it has already gotten a bit much now
-    foreach my $line (@lines) {
-	my ($name,$descrip)=split(/:/,$line);
-	if ($name ne $which ) { next; }
-	chomp($line);
-	my @config=split(/:/,$line);
-	$config{'name'}=$config[0];
-	$config{'description'}=$config[1];
-	$config{'CODElocation'}=$config[2];
-	$config{'CODEstart'}=$config[3];
-	$config{'CODElength'}=$config[4];
-	$config{'IDstart'}=$config[5];
-	$config{'IDlength'}=$config[6];
-	$config{'Qstart'}=$config[7];
- 	$config{'Qlength'}=$config[8];
-	$config{'Qoff'}=$config[9];
-	$config{'Qon'}=$config[10];
-	$config{'PaperID'}=$config[11];
-	$config{'PaperIDlength'}=$config[12];
-	$config{'FirstName'}=$config[13];
-	$config{'FirstNamelength'}=$config[14];
-	$config{'LastName'}=$config[15];
-	$config{'LastNamelength'}=$config[16];
-        $config{'BubblesPerRow'}=$config[17];
-	last;
-    }
-    return %config;
-}
-
 =pod 
 
 =item username_to_idmap
@@ -6248,7 +6110,7 @@
    Process a requested correction to a scanline.
 
   Arguments:
-    $scantron_config   - hash from &get_scantron_config()
+    $scantron_config   - hash from &Apache::lonnet::get_scantron_config()
     $scan_data         - hash of correction information 
                           (see &scantron_getfile())
     $line              - existing scanline
@@ -6931,7 +6793,7 @@
 
 sub scantron_process_corrections {
     my ($r) = @_;
-    my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+    my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
     my ($scanlines,$scan_data)=&scantron_getfile();
     my $classlist=&Apache::loncoursedata::get_classlist();
     my $which=$env{'form.scantron_line'};
@@ -7100,7 +6962,7 @@
 sub scantron_warning_screen {
     my ($button_text,$symb)=@_;
     my $title=&Apache::lonnet::gettitle($env{'form.selectpage'});
-    my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+    my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
     my $CODElist;
     if ($scantron_config{'CODElocation'} &&
 	$scantron_config{'CODEstart'} &&
@@ -7256,7 +7118,7 @@
     #get the student pick code ready
     $r->print(&Apache::loncommon::studentbrowser_javascript());
     my $nav_error;
-    my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+    my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
     my $max_bubble=&scantron_get_maxbubble(\$nav_error,\%scantron_config);
     if ($nav_error) {
         $r->print(&navmap_errormsg());
@@ -7716,7 +7578,7 @@
     my %idmap=&username_to_idmap($classlist);
 
     #get scantron line setup
-    my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+    my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
     my ($scanlines,$scan_data)=&scantron_getfile();
 
     my $nav_error;
@@ -8186,7 +8048,7 @@
 
  Arguments:
     $r           - Apache request object
-    $scan_config - hash from &get_scantron_config()
+    $scan_config - hash from &Apache::lonnet::get_scantron_config()
     $line        - Number of the line being displayed.
     $questionnum - Question number (may include subquestion)
     $error       - Type of error.
@@ -8350,7 +8212,7 @@
 
 sub scantron_validate_CODE {
     my ($r,$currentphase) = @_;
-    my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+    my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
     if ($scantron_config{'CODElocation'} &&
 	$scantron_config{'CODEstart'} &&
 	$scantron_config{'CODElength'}) {
@@ -8424,7 +8286,7 @@
         &Apache::lonnet::decode_symb($env{'form.selectpage'});
 
     #get scantron line setup
-    my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+    my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
     my ($scanlines,$scan_data)=&scantron_getfile();
 
     my $navmap = Apache::lonnavmaps::navmap->new();
@@ -8606,7 +8468,7 @@
         &Apache::lonnet::decode_symb($env{'form.selectpage'});
 
     #get scantron line setup
-    my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+    my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
     my ($scanlines,$scan_data)=&scantron_getfile();
 
     my $navmap = Apache::lonnavmaps::navmap->new();
@@ -8735,7 +8597,7 @@
         }
     }
     if ($needs_hand_bubbles) {
-        my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+        my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
         my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
         return &mt('The sequence to be graded contains response types which are handgraded.').'<p>'.
                &mt('If you have already graded these by bubbling sheets to indicate points awarded, [_1]what point value is assigned to a filled last bubble in each row?','<br />').
@@ -8754,7 +8616,7 @@
     }
     my $default_form_data=&defaultFormData($symb);
 
-    my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+    my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
     my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config); 
     my ($scanlines,$scan_data)=&scantron_getfile();
     my $classlist=&Apache::loncoursedata::get_classlist();
@@ -9149,6 +9011,7 @@
 sub scantron_upload_scantron_data {
     my ($r,$symb)=@_;
     my $dom = $env{'request.role.domain'};
+    my ($formatoptions,$formattitle,$formatjs) = &scantron_upload_dataformat($dom);
     my $domdesc = &Apache::lonnet::domain($dom,'description');
     $r->print(&Apache::loncommon::coursebrowser_javascript($dom));
     my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid',
@@ -9188,6 +9051,7 @@
         return;
     }
 
+    '.$formatjs.'
 '));
     $r->print('
 <h3>'.&mt('Send bubblesheet data to a course').'</h3>
@@ -9203,7 +9067,12 @@
   &Apache::lonhtmlcommon::row_closure().
   &Apache::lonhtmlcommon::row_title(&mt('Domain')).
   '<input name="domainid" type="hidden" />'.$domdesc.
-  &Apache::lonhtmlcommon::row_closure().
+  &Apache::lonhtmlcommon::row_closure());
+    if ($formatoptions) {
+        $r->print(&Apache::lonhtmlcommon::row_title($formattitle).$formatoptions.
+                  &Apache::lonhtmlcommon::row_closure());
+    }
+    $r->print(
   &Apache::lonhtmlcommon::row_title(&mt('File to upload')).
   '<input type="file" name="upfile" size="50" />'.
   &Apache::lonhtmlcommon::row_closure(1).
@@ -9216,6 +9085,78 @@
     return '';
 }
 
+sub scantron_upload_dataformat {
+    my ($dom) = @_;
+    my ($formatoptions,$formattitle,$formatjs);
+    $formatjs = <<'END';
+function toggleScantab(form) {
+   return;
+}
+END
+    my %domconfig = &Apache::lonnet::get_dom('configuration',['scantron'],$dom);
+    if (ref($domconfig{'scantron'}) eq 'HASH') {
+        if (ref($domconfig{'scantron'}{'config'}) eq 'HASH') {
+            if (keys(%{$domconfig{'scantron'}{'config'}}) > 1) {
+                if (($domconfig{'scantron'}{'config'}{'dat'}) &&
+                    (ref($domconfig{'scantron'}{'config'}{'csv'}) eq 'HASH')) {
+                    if (keys(%{$domconfig{'scantron'}{'config'}{'csv'}})) {
+                        my ($onclick,$formatextra,$singleline);
+                        my @lines = &Apache::lonnet::get_scantronformat_file();
+                        my $count = 0;
+                        foreach my $line (@lines) {
+                            next if ($line =~ /^#/);
+                            $singleline = $line;
+                            $count ++;
+                        }
+                        if ($count > 1) {
+                            $formatextra = '<div style="display:none" id="bubbletype">'.
+                                           &scantron_scantab().'</div>';
+                            $onclick = ' onclick="toggleScantab(this.form);"';
+                            $formatjs = <<"END";
+function toggleScantab(form) {
+    var divid = 'bubbletype';
+    if (document.getElementById(divid)) {
+        var radioname = 'fileformat';
+        var num = form.elements[radioname].length;
+        if (num) {
+            for (var i=0; i<num; i++) {
+                if (form.elements[radioname][i].checked) {
+                    var chosen = form.elements[radioname][i].value;
+                    if (chosen == 'dat') {
+                        document.getElementById(divid).style.display = 'none';
+                    } else if (chosen == 'csv') {
+                        document.getElementById(divid).style.display = 'inline-block';
+                    }
+                }
+            }
+        }
+    }
+    return;
+}
+
+END
+                        } elsif ($count == 1) {
+                            my $formatname = (split(/:/,$singleline,2))[0];
+                            $formatextra = '<input type="hidden" name="scantron_format" value="'.$formatname.'" />';
+                        }
+                        $formattitle = &mt('File format');
+                        $formatoptions = '<label><input name="fileformat" type="radio" value="dat" checked="checked"'.$onclick.' />'.
+                                         &mt('Plain Text (no delimiters)').
+                                         '</label>'.(' 'x2).
+                                         '<label><input name="fileformat" type="radio" value="csv"'.$onclick.' />'.
+                                         &mt('Comma separated values').'</label>'.$formatextra;
+                    }
+                }
+            } elsif (keys(%{$domconfig{'scantron'}{'config'}}) == 1) {
+                if (keys(%{$domconfig{'scantron'}{'config'}{'csv'}})) {
+                    $formattitle = &mt('Format of bubblesheet data file:');
+                    $formatoptions = &scantron_scantab();
+                }
+            }
+        }
+    }
+    return ($formatoptions,$formattitle,$formatjs);
+}
 
 sub scantron_upload_scantron_data_save {
     my($r,$symb)=@_;
@@ -9242,8 +9183,34 @@
                 &mt('The file: [_1] you attempted to upload contained no information. Please check that you entered the correct filename.',
                         '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'),1));
     } else {
-        my $result = 
-            &Apache::lonnet::userfileupload('upfile','','scantron','','','',
+        my %domconfig = &Apache::lonnet::get_dom('configuration',['scantron'],$env{'form.domainid'});
+        my $parser;
+        if (ref($domconfig{'scantron'}) eq 'HASH') {
+            if (ref($domconfig{'scantron'}{'config'}) eq 'HASH') {
+                my $is_csv;
+                my @possibles = keys(%{$domconfig{'scantron'}{'config'}});
+                if (@possibles > 1) {
+                    if ($env{'form.fileformat'} eq 'csv') {
+                        if (ref($domconfig{'scantron'}{'config'}{'csv'}) eq 'HASH') {
+                            if (keys(%{$domconfig{'scantron'}{'config'}{'csv'}}) > 1) {
+                                $is_csv = 1;
+                            }
+                        }
+                    }
+                } elsif (@possibles == 1) {
+                    if (ref($domconfig{'scantron'}{'config'}{'csv'}) eq 'HASH') {
+                        if (keys(%{$domconfig{'scantron'}{'config'}{'csv'}}) > 1) {
+                            $is_csv = 1;
+                        }
+                    }
+                }
+                if ($is_csv) {
+                   $parser = $domconfig{'scantron'}{'config'}{'csv'};
+                }
+            }
+        }
+        my $result =
+            &Apache::lonnet::userfileupload('upfile','scantron','scantron',$parser,'','',
                                             $env{'form.courseid'},$env{'form.domainid'});
         if ($result =~ m{^/uploaded/}) {
             $r->print(
@@ -9288,7 +9255,7 @@
             $idmap{$lckey} = $idmap{$key};
         }
         my %unique_formats;
-        my @formatlines = &get_scantronformat_file();
+        my @formatlines = &Apache::lonnet::get_scantronformat_file();
         foreach my $line (@formatlines) {
             chomp($line);
             my @config = split(/:/,$line);
@@ -9436,7 +9403,7 @@
     my (undef, undef, $sequence) = &Apache::lonnet::decode_symb($env{'form.selectpage'});
     my %record;
     my %scantron_config =
-        &Apache::grades::get_scantron_config($env{'form.scantron_format'});
+        &Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
     my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
     my ($scanlines,$scan_data)=&Apache::grades::scantron_getfile();
     my $classlist=&Apache::loncoursedata::get_classlist();
@@ -10668,13 +10635,21 @@
 }
 
 sub startpage {
-    my ($r,$symb,$crumbs,$onlyfolderflag,$nodisplayflag,$stuvcurrent,$stuvdisp,$nomenu,$js) = @_;
+    my ($r,$symb,$crumbs,$onlyfolderflag,$nodisplayflag,$stuvcurrent,$stuvdisp,$nomenu,$js,$onload) = @_;
+    my %args;
+    if ($onload) {
+         my %loaditems = (
+                        'onload' => $onload,
+                      );
+         $args{'add_entries'} = \%loaditems;
+    }
     if ($nomenu) {
-        $r->print(&Apache::loncommon::start_page("Student's Version",$js,{'only_body' => '1'}));
+        $args{'only_body'} = 1; 
+        $r->print(&Apache::loncommon::start_page("Student's Version",$js,\%args);
     } else {
         unshift(@$crumbs,{href=>&href_symb_cmd($symb,'gradingmenu'),text=>"Grading"});
-        $r->print(&Apache::loncommon::start_page('Grading',$js,
-                                                 {'bread_crumbs' => $crumbs}));
+        $args{'bread_crumbs'} = $crumbs;
+        $r->print(&Apache::loncommon::start_page('Grading',$js,\%args));
         &Apache::lonquickgrades::startGradeScreen($r,($env{'form.symb'}?'probgrading':'grading'));
     }
     unless ($nodisplayflag) {
@@ -10858,7 +10833,8 @@
             &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
 	    $request->print(&csvuploadassign($request,$symb));
 	} elsif ($command eq 'scantron_selectphase' && $perm{'mgr'}) {
-            &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
+            &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1,
+                       undef,undef,undef,undef,'toggleScantab(document.rules);');
 	    $request->print(&scantron_selectphase($request,undef,$symb));
  	} elsif ($command eq 'scantron_warning' && $perm{'mgr'}) {
             &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
@@ -10872,7 +10848,8 @@
  	} elsif ($command eq 'scantronupload' && 
  		 (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
 		  &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {
-            &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
+            &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1,
+                       undef,undef,undef,undef,'toggleScantab(document.rules);');
  	    $request->print(&scantron_upload_scantron_data($request,$symb)); 
  	} elsif ($command eq 'scantronupload_save' &&
  		 (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
Index: loncom/lonnet/perl/lonnet.pm
diff -u loncom/lonnet/perl/lonnet.pm:1.1400 loncom/lonnet/perl/lonnet.pm:1.1401
--- loncom/lonnet/perl/lonnet.pm:1.1400	Sat Dec 29 16:50:06 2018
+++ loncom/lonnet/perl/lonnet.pm	Sun Jan 27 14:40:02 2019
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.1400 2018/12/29 16:50:06 raeburn Exp $
+# $Id: lonnet.pm,v 1.1401 2019/01/27 14:40:02 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -3905,13 +3905,16 @@
 # input: $formname - the contents of the file are in $env{"form.$formname"}
 #                    the desired filename is in $env{"form.$formname.filename"}
 #        $context - possible values: coursedoc, existingfile, overwrite, 
-#                                    canceloverwrite, or ''. 
+#                                    canceloverwrite, scantron or ''.
 #                   if 'coursedoc': upload to the current course
 #                   if 'existingfile': write file to tmp/overwrites directory 
 #                   if 'canceloverwrite': delete file written to tmp/overwrites directory
 #                   $context is passed as argument to &finishuserfileupload
 #        $subdir - directory in userfile to store the file into
-#        $parser - instruction to parse file for objects ($parser = parse)    
+#        $parser - instruction to parse file for objects ($parser = parse) or
+#                  if context is 'scantron', $parser is hashref of csv column mapping
+#                  (e.g.,{ PaperID => 0, LastName => 1, FirstName => 2, ID => 3, 
+#                          Section => 4, CODE => 5, FirstQuestion => 9 }).
 #        $allfiles - reference to hash for embedded objects
 #        $codebase - reference to hash for codebase of java objects
 #        $desuname - username for permanent storage of uploaded file
@@ -4101,7 +4104,7 @@
             }
         }
     }
-    if ($parser eq 'parse') {
+    if (($context ne 'scantron') && ($parser eq 'parse')) {
         if ((ref($mimetype)) && ($$mimetype eq 'text/html')) {
             my $parse_result = &extract_embedded_items($filepath.'/'.$file,
                                                        $allfiles,$codebase);
@@ -4110,6 +4113,9 @@
 	   	         ' for embedded media: '.$parse_result); 
             }
         }
+    } elsif (($context eq 'scantron') && (ref($parser) eq 'HASH')) {
+        my $format = $env{'form.scantron_format'};
+        &bubblesheet_converter($docudom,$filepath.'/'.$file,$parser,$format);
     }
     if (($thumbwidth =~ /^\d+$/) && ($thumbheight =~ /^\d+$/)) {
         my $input = $filepath.'/'.$file;
@@ -4350,6 +4356,172 @@
     return;
 }
 
+sub bubblesheet_converter {
+    my ($cdom,$fullpath,$config,$format) = @_;
+    if ((&domain($cdom) ne '') &&
+        ($fullpath =~ m{^\Q$perlvar{'lonDocRoot'}/userfiles/$cdom/$match_courseid/scantron_orig}) &&
+        (-e $fullpath) && (ref($config) eq 'HASH') && ($format ne '')) {
+        my %csvcols = %{$config};
+        my %csvbynum = reverse(%csvcols);
+        my %scantronconf = &get_scantron_config($format,$cdom);
+        if (keys(%scantronconf)) {
+            my %bynum = (
+                          $scantronconf{CODEstart} => 'CODEstart',
+                          $scantronconf{IDstart}   => 'IDstart',
+                          $scantronconf{PaperID}   => 'PaperID',
+                          $scantronconf{FirstName} => 'FirstName',
+                          $scantronconf{LastName}  => 'LastName',
+                          $scantronconf{Qstart}    => 'Qstart',
+                        );
+            my @ordered;
+            foreach my $item (sort { $a <=> $b } keys(%bynum)) {
+                push (@ordered,$bynum{$item}));
+            }
+            my %mapstart = (
+                              CODEstart => 'CODE',
+                              IDstart   => 'ID',
+                              PaperID   => 'PaperID',
+                              FirstName => 'FirstName',
+                              LastName  => 'LastName',
+                              Qstart    => 'FirstQuestion',
+                           );
+            my %maplength = (
+                              CODEstart => 'CODElength',
+                              IDstart   => 'IDlength',
+                              PaperID   => 'PaperIDlength',
+                              FirstName => 'FirstNamelength',
+                              LastName  => 'LastNamelength',
+            );
+            if (open(my $fh,'<',$fullpath)) {
+                my $output;
+                while (my $line=<$fh>) {
+                    $line =~ s{[\r\n]+$}{};
+                    my %found;
+                    my @values = split(/,/,$line);
+                    my ($qstart,$record);
+                    for (my $i=0; $i<@values; $i++) {
+                        if (($qstart ne '') && ($i > $qstart)) {
+                            $found{'FirstQuestion'} .= $values[$i];
+                        } elsif (exists($csvbynum{$i})) {
+                            if ($csvbynum{$i} eq 'FirstQuestion') {
+                                $qstart = $i;
+                            } else {
+                                $values[$i] =~ s/^\s+//;
+                                if ($csvbynum{$i} eq 'PaperID') {
+                                    while (length($values[$i]) < $scantronconf{$maplength{$csvbynum{$i}}}) {
+                                        $values[$i] = '0'.$values[$i];
+                                    }
+                                }
+                            }
+                            $found{$csvbynum{$i}} = $values[$i];
+                        }
+                    }
+                    foreach my $item (@ordered) {
+                        my $currlength = 1+length($record);
+                        my $numspaces = $scantronconf{$item} - $currlength;
+                        if ($numspaces > 0) {
+                            $record .= (' ' x $numspaces);
+                        }
+                        if (($mapstart{$item} ne '') && (exists($found{$mapstart{$item}}))) {
+                            unless ($item eq 'Qstart') {
+                                if (length($found{$mapstart{$item}}) > $scantronconf{$maplength{$item}}) {
+                                    $found{$mapstart{$item}} = substr($found{$mapstart{$item}},0,$scantronconf{$maplength{$item}});
+                                }
+                            }
+                            $record .= $found{$mapstart{$item}};
+                        }
+                    }
+                    $output .= "$record\n";
+                }
+                close($fh);
+                if ($output) {
+                    if (open(my $fh,'>',$fullpath)) {
+                        print $fh $output;
+                        close($fh);
+                    }
+                }
+            }
+        }
+        return;
+    }
+}
+
+sub get_scantron_config {
+    my ($which,$cdom) = @_;
+    my @lines = &get_scantronformat_file($cdom);
+    my %config;
+    #FIXME probably should move to XML it has already gotten a bit much now
+    foreach my $line (@lines) {
+        my ($name,$descrip)=split(/:/,$line);
+        if ($name ne $which ) { next; }
+        chomp($line);
+        my @config=split(/:/,$line);
+        $config{'name'}=$config[0];
+        $config{'description'}=$config[1];
+        $config{'CODElocation'}=$config[2];
+        $config{'CODEstart'}=$config[3];
+        $config{'CODElength'}=$config[4];
+        $config{'IDstart'}=$config[5];
+        $config{'IDlength'}=$config[6];
+        $config{'Qstart'}=$config[7];
+        $config{'Qlength'}=$config[8];
+        $config{'Qoff'}=$config[9];
+        $config{'Qon'}=$config[10];
+        $config{'PaperID'}=$config[11];
+        $config{'PaperIDlength'}=$config[12];
+        $config{'FirstName'}=$config[13];
+        $config{'FirstNamelength'}=$config[14];
+        $config{'LastName'}=$config[15];
+        $config{'LastNamelength'}=$config[16];
+        $config{'BubblesPerRow'}=$config[17];
+        last;
+    }
+    return %config;
+}
+
+sub get_scantronformat_file {
+    my ($cdom) = @_;
+    if ($cdom eq '') {
+        $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};
+    }
+    my %domconfig = &get_dom('configuration',['scantron'],$cdom);
+    my $gottab = 0;
+    my @lines;
+    if (ref($domconfig{'scantron'}) eq 'HASH') {
+        if ($domconfig{'scantron'}{'scantronformat'} ne '') {
+            my $formatfile = &getfile($perlvar{'lonDocRoot'}.$domconfig{'scantron'}{'scantronformat'});
+            if ($formatfile ne '-1') {
+                @lines = split("\n",$formatfile,-1);
+                $gottab = 1;
+            }
+        }
+    }
+    if (!$gottab) {
+        my $confname = $cdom.'-domainconfig';
+        my $default = $perlvar{'lonDocRoot'}.'/res/'.$cdom.'/'.$confname.'/default.tab';
+        my $formatfile = &getfile($default);
+        if ($formatfile ne '-1') {
+            @lines = split("\n",$formatfile,-1);
+            $gottab = 1;
+        }
+    }
+    if (!$gottab) {
+        my @domains = &current_machine_domains();
+        if (grep(/^\Q$cdom\E$/, at domains)) {
+            if (open(my $fh,'<',$perlvar{'lonTabDir'}.'/scantronformat.tab')) {
+                @lines = <$fh>;
+                close($fh);
+            }  
+        } else {
+            if (open(my $fh,'<',$perlvar{'lonTabDir'}.'/default_scantronformat.tab')) {
+                @lines = <$fh>;
+                close($fh);
+            }
+        }
+    }
+    return @lines;
+}
+
 sub removeuploadedurl {
     my ($url)=@_;	
     my (undef,undef,$udom,$uname,$fname)=split('/',$url,5);    
@@ -15135,6 +15307,89 @@
 
 =back
 
+=head2 Bubblesheet Configuration
+
+=over 4
+
+=item *
+
+get_scantron_config($which)
+
+$which - the name of the configuration to parse from the file.
+
+Parses and returns the bubblesheet configuration line selected as a
+hash of configuration file fields.
+
+
+Returns:
+    If the named configuration is not in the file, an empty
+    hash is returned.
+
+    a hash with the fields
+      name         - internal name for the this configuration setup
+      description  - text to display to operator that describes this config
+      CODElocation - if 0 or the string 'none'
+                          - no CODE exists for this config
+                     if -1 || the string 'letter'
+                          - a CODE exists for this config and is
+                            a string of letters
+                     Unsupported value (but planned for future support)
+                          if a positive integer
+                               - The CODE exists as the first n items from
+                                 the question section of the form
+                          if the string 'number'
+                               - The CODE exists for this config and is
+                                 a string of numbers
+      CODEstart   - (only matter if a CODE exists) column in the line where
+                     the CODE starts
+      CODElength  - length of the CODE
+      IDstart     - column where the student/employee ID starts
+      IDlength    - length of the student/employee ID info
+      Qstart      - column where the information from the bubbled
+                    'questions' start
+      Qlength     - number of columns comprising a single bubble line from
+                    the sheet. (usually either 1 or 10)
+      Qon         - either a single character representing the character used
+                    to signal a bubble was chosen in the positional setup, or
+                    the string 'letter' if the letter of the chosen bubble is
+                    in the final, or 'number' if a number representing the
+                    chosen bubble is in the file (1->A 0->J)
+      Qoff        - the character used to represent that a bubble was
+                    left blank
+      PaperID     - if the scanning process generates a unique number for each
+                    sheet scanned the column that this ID number starts in
+      PaperIDlength - number of columns that comprise the unique ID number
+                      for the sheet of paper
+      FirstName   - column that the first name starts in
+      FirstNameLength - number of columns that the first name spans
+
+      LastName    - column that the last name starts in
+      LastNameLength - number of columns that the last name spans
+      BubblesPerRow - number of bubbles available in each row used to
+                      bubble an answer. (If not specified, 10 assumed).
+
+
+=item *
+
+get_scantronformat_file($cdom)
+
+$cdom - the course's domain (optional); if not supplied, uses
+domain for current $env{'request.course.id'}.
+
+Returns an array containing lines from the scantron format file for
+the domain of the course.
+
+If a url for a custom.tab file is listed in domain's configuration.db,
+lines are from this file.
+
+Otherwise, if a default.tab has been published in RES space by the
+domainconfig user, lines are from this file.
+
+Otherwise, fall back to getting lines from the legacy file on the
+local server:  /home/httpd/lonTabs/default_scantronformat.tab
+
+=back
+
 =head2 Resource Subroutines
 
 =over 4
@@ -15832,6 +16087,7 @@
   formname: same as for userfileupload()
   fname: filename (including subdirectories) for the file
   parser: if 'parse', will parse (html) file to extract references to objects, links etc.
+          if hashref, and context is scantron, will convert csv format to standard format
   allfiles: reference to hash used to store objects found by parser
   codebase: reference to hash used for codebases of java objects found by parser
   thumbwidth: width (pixels) of thumbnail to be created for uploaded image


More information about the LON-CAPA-cvs mailing list