File:  [LON-CAPA] / loncom / interface / Attic / lonspreadsheet.pm
Revision 1.27: download - view: text, annotated - select for diffs
Tue Jan 2 12:12:43 2001 UTC (23 years, 6 months ago) by www
Branches: MAIN
CVS tags: HEAD
Better kaputt than dysfunctional

    1: # The LearningOnline Network with CAPA
    2: # Spreadsheet/Grades Display Handler
    3: #
    4: # 11/11,11/15,11/27,12/04,12/05,12/06,12/07,
    5: # 12/08,12/09,12/11,12/12,12/15,12/16,12/18,12/19,12/30,
    6: # 01/01/01,02/01 Gerd Kortemeyer
    7: 
    8: package Apache::lonspreadsheet;
    9: 
   10: use strict;
   11: use Safe;
   12: use Safe::Hole;
   13: use Opcode;
   14: use Apache::lonnet;
   15: use Apache::Constants qw(:common :http);
   16: use GDBM_File;
   17: use HTML::TokeParser;
   18: 
   19: #
   20: # These cache hashes need to be independent of user, resource and course
   21: # (user and course can/should be in the keys)
   22: #
   23: use vars qw(%spreadsheets %courserdatas %userrdatas %defaultsheets);
   24: 
   25: #
   26: # These global hashes are dependent on user, course and resource, 
   27: # and need to be initialized every time when a sheet is calculated
   28: #
   29: my %courseopt;
   30: my %useropt;
   31: my %parmhash;
   32: 
   33: # =============================================================================
   34: # ===================================== Implements an instance of a spreadsheet
   35: 
   36: sub initsheet {
   37:     my $safeeval = new Safe;
   38:     my $safehole = new Safe::Hole;
   39:     $safeeval->permit("entereval");
   40:     $safeeval->permit(":base_math");
   41:     $safeeval->permit("sort");
   42:     $safeeval->deny(":base_io");
   43:     $safehole->wrap(\&Apache::lonnet::EXT,$safeeval,'&EXT');
   44:     my $code=<<'ENDDEFS';
   45: # ---------------------------------------------------- Inside of the safe space
   46: 
   47: #
   48: # f: formulas
   49: # t: intermediate format (variable references expanded)
   50: # v: output values
   51: # c: preloaded constants (A-column)
   52: # rl: row label
   53: 
   54: %v=(); 
   55: %t=();
   56: %f=();
   57: %c=();
   58: %rl=();
   59: 
   60: $maxrow=0;
   61: $sheettype='';
   62: 
   63: # filename/reference of the sheet
   64: 
   65: $filename='';
   66: 
   67: # user data
   68: $uname='';
   69: $uhome='';
   70: $udom='';
   71: 
   72: # course data
   73: 
   74: $csec='';
   75: $chome='';
   76: $cnum='';
   77: $cdom='';
   78: 
   79: # symb
   80: 
   81: $usymb='';
   82: 
   83: sub mask {
   84:     my ($lower,$upper)=@_;
   85: 
   86:     $lower=~/([A-Za-z]|\*)(\d+|\*)/;
   87:     my $la=$1;
   88:     my $ld=$2;
   89: 
   90:     $upper=~/([A-Za-z]|\*)(\d+|\*)/;
   91:     my $ua=$1;
   92:     my $ud=$2;
   93:     my $alpha='';
   94:     my $num='';
   95: 
   96:     if (($la eq '*') || ($ua eq '*')) {
   97:        $alpha='[A-Za-z]';
   98:     } else {
   99:        if (($la=~/[A-Z]/) && ($ua=~/[A-Z]/) ||
  100:            ($la=~/[a-z]/) && ($ua=~/[a-z]/)) {
  101:           $alpha='['.$la.'-'.$ua.']';
  102:        } else {
  103:           $alpha='['.$la.'-Za-'.$ua.']';
  104:        }
  105:     }   
  106: 
  107:     if (($ld eq '*') || ($ud eq '*')) {
  108: 	$num='\d+';
  109:     } else {
  110:         if (length($ld)!=length($ud)) {
  111:            $num.='(';
  112: 	   map {
  113:               $num.='['.$_.'-9]';
  114:            } ($ld=~m/\d/g);
  115:            if (length($ud)-length($ld)>1) {
  116:               $num.='|\d{'.(length($ld)+1).','.(length($ud)-1).'}';
  117: 	   }
  118:            $num.='|';
  119:            map {
  120:                $num.='[0-'.$_.']';
  121:            } ($ud=~m/\d/g);
  122:            $num.=')';
  123:        } else {
  124:            my @lda=($ld=~m/\d/g);
  125:            my @uda=($ud=~m/\d/g);
  126:            my $i; $j=0; $notdone=1;
  127:            for ($i=0;($i<=$#lda)&&($notdone);$i++) {
  128:                if ($lda[$i]==$uda[$i]) {
  129: 		   $num.=$lda[$i];
  130:                    $j=$i;
  131:                } else {
  132:                    $notdone=0;
  133:                }
  134:            }
  135:            if ($j<$#lda-1) {
  136: 	       $num.='('.$lda[$j+1];
  137:                for ($i=$j+2;$i<=$#lda;$i++) {
  138:                    $num.='['.$lda[$i].'-9]';
  139:                }
  140:                if ($uda[$j+1]-$lda[$j+1]>1) {
  141: 		   $num.='|['.($lda[$j+1]+1).'-'.($uda[$j+1]-1).']\d{'.
  142:                    ($#lda-$j-1).'}';
  143:                }
  144: 	       $num.='|'.$uda[$j+1];
  145:                for ($i=$j+2;$i<=$#uda;$i++) {
  146:                    $num.='[0-'.$uda[$i].']';
  147:                }
  148:                $num.=')';
  149:            } else {
  150:                if ($lda[$#lda]!=$uda[$#uda]) {
  151:                   $num.='['.$lda[$#lda].'-'.$uda[$#uda].']';
  152: 	       }
  153:            }
  154:        }
  155:     }
  156:     return '^'.$alpha.$num."\$";
  157: }
  158: 
  159: sub NUM {
  160:     my $mask=mask(@_);
  161:     my $num=0;
  162:     map {
  163:         $num++;
  164:     } grep /$mask/,keys %v;
  165:     return $num;   
  166: }
  167: 
  168: sub BIN {
  169:     my ($low,$high,$lower,$upper)=@_;
  170:     my $mask=mask($lower,$upper);
  171:     my $num=0;
  172:     map {
  173:         if (($v{$_}>=$low) && ($v{$_}<=$high)) {
  174:             $num++;
  175:         }
  176:     } grep /$mask/,keys %v;
  177:     return $num;   
  178: }
  179: 
  180: 
  181: sub SUM {
  182:     my $mask=mask(@_);
  183:     my $sum=0;
  184:     map {
  185:         $sum+=$v{$_};
  186:     } grep /$mask/,keys %v;
  187:     return $sum;   
  188: }
  189: 
  190: sub MEAN {
  191:     my $mask=mask(@_);
  192:     my $sum=0; my $num=0;
  193:     map {
  194:         $sum+=$v{$_};
  195:         $num++;
  196:     } grep /$mask/,keys %v;
  197:     if ($num) {
  198:        return $sum/$num;
  199:     } else {
  200:        return undef;
  201:     }   
  202: }
  203: 
  204: sub STDDEV {
  205:     my $mask=mask(@_);
  206:     my $sum=0; my $num=0;
  207:     map {
  208:         $sum+=$v{$_};
  209:         $num++;
  210:     } grep /$mask/,keys %v;
  211:     unless ($num>1) { return undef; }
  212:     my $mean=$sum/$num;
  213:     $sum=0;
  214:     map {
  215:         $sum+=($v{$_}-$mean)**2;
  216:     } grep /$mask/,keys %v;
  217:     return sqrt($sum/($num-1));    
  218: }
  219: 
  220: sub PROD {
  221:     my $mask=mask(@_);
  222:     my $prod=1;
  223:     map {
  224:         $prod*=$v{$_};
  225:     } grep /$mask/,keys %v;
  226:     return $prod;   
  227: }
  228: 
  229: sub MAX {
  230:     my $mask=mask(@_);
  231:     my $max='-';
  232:     map {
  233:         unless ($max) { $max=$v{$_}; }
  234:         if (($v{$_}>$max) || ($max eq '-')) { $max=$v{$_}; }
  235:     } grep /$mask/,keys %v;
  236:     return $max;   
  237: }
  238: 
  239: sub MIN {
  240:     my $mask=mask(@_);
  241:     my $min='-';
  242:     map {
  243:         unless ($max) { $max=$v{$_}; }
  244:         if (($v{$_}<$min) || ($min eq '-')) { $min=$v{$_}; }
  245:     } grep /$mask/,keys %v;
  246:     return $min;   
  247: }
  248: 
  249: sub SUMMAX {
  250:     my ($num,$lower,$upper)=@_;
  251:     my $mask=mask($lower,$upper);
  252:     my @inside=();
  253:     map {
  254: 	$inside[$#inside+1]=$v{$_};
  255:     } grep /$mask/,keys %v;
  256:     @inside=sort(@inside);
  257:     my $sum=0; my $i;
  258:     for ($i=$#inside;(($i>$#inside-$num) && ($i>=0));$i--) { 
  259:         $sum+=$inside[$i];
  260:     }
  261:     return $sum;   
  262: }
  263: 
  264: sub SUMMIN {
  265:     my ($num,$lower,$upper)=@_;
  266:     my $mask=mask($lower,$upper);
  267:     my @inside=();
  268:     map {
  269: 	$inside[$#inside+1]=$v{$_};
  270:     } grep /$mask/,keys %v;
  271:     @inside=sort(@inside);
  272:     my $sum=0; my $i;
  273:     for ($i=0;(($i<$num) && ($i<=$#inside));$i++) { 
  274:         $sum+=$inside[$i];
  275:     }
  276:     return $sum;   
  277: }
  278: 
  279: sub sett {
  280:     %t=();
  281:     my $pattern='';
  282:     if ($sheettype eq 'assesscalc') {
  283: 	$pattern='A';
  284:     } else {
  285:         $pattern='[A-Z]';
  286:     }
  287:     map {
  288: 	if ($_=~/template\_(\w)/) {
  289: 	  my $col=$1;
  290:           unless ($col=~/^$pattern/) {
  291:             map {
  292: 	      if ($_=~/A(\d+)/) {
  293: 		my $trow=$1;
  294:                 if ($trow) {
  295: 		    my $lb=$col.$trow;
  296:                     $t{$lb}=$f{'template_'.$col};
  297:                     $t{$lb}=~s/\#/$trow/g;
  298:                     $t{$lb}=~s/\.\.+/\,/g;
  299:                     $t{$lb}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$v\{\'$2\'\}/g;
  300:                 }
  301: 	      }
  302:             } keys %f;
  303: 	  }
  304:       }
  305:     } keys %f;
  306:     map {
  307: 	if (($f{$_}) && ($_!~/template\_/)) {
  308:             if ($_=~/^$pattern/) {
  309: 	        unless ($f{$_}=~/^\!/) {
  310: 		    $t{$_}=$c{$_};
  311:                 }
  312:             } else {
  313: 	       $t{$_}=$f{$_};
  314:                $t{$_}=~s/\.\.+/\,/g;
  315:                $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$v\{\'$2\'\}/g;
  316:             }
  317:         }
  318:     } keys %f;
  319:     $t{'A0'}=$f{'A0'};
  320:     $t{'A0'}=~s/\.\.+/\,/g;
  321:     $t{'A0'}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$v\{\'$2\'\}/g;
  322: }
  323: 
  324: sub calc {
  325:     %v=();
  326:     &sett();
  327:     my $notfinished=1;
  328:     my $depth=0;
  329:     while ($notfinished) {
  330: 	$notfinished=0;
  331:         map {
  332:             my $old=$v{$_};
  333:             $v{$_}=eval($t{$_});
  334: 	    if ($@) {
  335: 		%v=();
  336:                 return $@;
  337:             }
  338: 	    if ($v{$_} ne $old) { $notfinished=1; }
  339:         } keys %t;
  340:         $depth++;
  341:         if ($depth>100) {
  342: 	    %v=();
  343:             return 'Maximum calculation depth exceeded';
  344:         }
  345:     }
  346:     return '';
  347: }
  348: 
  349: sub templaterow {
  350:     my @cols=();
  351:     $cols[0]='<b><font size=+1>Template</font></b>';
  352:     map {
  353:         my $fm=$f{'template_'.$_};
  354:         $fm=~s/[\'\"]/\&\#34;/g;
  355:         $cols[$#cols+1]="'template_$_','$fm'".'___eq___'.$fm;
  356:     } ('A','B','C','D','E','F','G','H','I','J','K','L','M',
  357:        'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
  358:        'a','b','c','d','e','f','g','h','i','j','k','l','m',
  359:        'n','o','p','q','r','s','t','u','v','w','x','y','z');
  360:     return @cols;
  361: }
  362: 
  363: sub outrowassess {
  364:     my $n=shift;
  365:     my @cols=();
  366:     if ($n) {
  367:        $cols[0]=$rl{$f{'A'.$n}};
  368:     } else {
  369:        $cols[0]='<b><font size=+1>Export</font></b>';
  370:     }
  371:     map {
  372:         my $fm=$f{$_.$n};
  373:         $fm=~s/[\'\"]/\&\#34;/g;
  374:         $cols[$#cols+1]="'$_$n','$fm'".'___eq___'.$v{$_.$n};
  375:     } ('A','B','C','D','E','F','G','H','I','J','K','L','M',
  376:        'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
  377:        'a','b','c','d','e','f','g','h','i','j','k','l','m',
  378:        'n','o','p','q','r','s','t','u','v','w','x','y','z');
  379:     return @cols;
  380: }
  381: 
  382: sub outrow {
  383:     my $n=shift;
  384:     my @cols=();
  385:     if ($n) {
  386:        $cols[0]=$rl{$f{'A'.$n}};
  387:     } else {
  388:        $cols[0]='<b><font size=+1>Export</font></b>';
  389:     }
  390:     map {
  391:         my $fm=$f{$_.$n};
  392:         $fm=~s/[\'\"]/\&\#34;/g;
  393:         $cols[$#cols+1]="'$_$n','$fm'".'___eq___'.$v{$_.$n};
  394:     } ('A','B','C','D','E','F','G','H','I','J','K','L','M',
  395:        'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
  396:        'a','b','c','d','e','f','g','h','i','j','k','l','m',
  397:        'n','o','p','q','r','s','t','u','v','w','x','y','z');
  398:     return @cols;
  399: }
  400: 
  401: sub exportrowa {
  402:     my $rowa='';
  403:     map {
  404: 	$rowa.=$v{$_.'0'}."___;___";
  405:     } ('A','B','C','D','E','F','G','H','I','J','K','L','M',
  406:        'N','O','P','Q','R','S','T','U','V','W','X','Y','Z');
  407:     $rowa=~s/\_\_\_\;\_\_\_$//;
  408:     return $rowa;
  409: }
  410: 
  411: # ------------------------------------------- End of "Inside of the safe space"
  412: ENDDEFS
  413:     $safeeval->reval($code);
  414:     return $safeeval;
  415: }
  416: 
  417: # ------------------------------------------------ Add or change formula values
  418: 
  419: sub setformulas {
  420:     my ($safeeval,@f)=@_;
  421:     $safeeval->reval('%f='."('".join("','",@f)."');");
  422: }
  423: 
  424: # ------------------------------------------------ Add or change formula values
  425: 
  426: sub setconstants {
  427:     my ($safeeval,@c)=@_;
  428:     $safeeval->reval('%c='."('".join("','",@c)."');");
  429: }
  430: 
  431: # ------------------------------------------------ Add or change formula values
  432: 
  433: sub setrowlabels {
  434:     my ($safeeval,@rl)=@_;
  435:     $safeeval->reval('%rl='."('".join("','",@rl)."');");
  436: }
  437: 
  438: # ------------------------------------------------------- Calculate spreadsheet
  439: 
  440: sub calcsheet {
  441:     my $safeeval=shift;
  442:     $safeeval->reval('&calc();');
  443: }
  444: 
  445: # ------------------------------------------------------------------ Get values
  446: 
  447: sub getvalues {
  448:     my $safeeval=shift;
  449:     return $safeeval->reval('%v');
  450: }
  451: 
  452: # ---------------------------------------------------------------- Get formulas
  453: 
  454: sub getformulas {
  455:     my $safeeval=shift;
  456:     return $safeeval->reval('%f');
  457: }
  458: 
  459: # -------------------------------------------------------------------- Set type
  460: 
  461: sub settype {
  462:     my ($safeeval,$type)=@_;
  463:     $safeeval->reval('$sheettype="'.$type.'";');
  464: }
  465: 
  466: # -------------------------------------------------------------------- Get type
  467: 
  468: sub gettype {
  469:     my $safeeval=shift;
  470:     return $safeeval->reval('$sheettype');
  471: }
  472: 
  473: # ------------------------------------------------------------------ Set maxrow
  474: 
  475: sub setmaxrow {
  476:     my ($safeeval,$row)=@_;
  477:     $safeeval->reval('$maxrow='.$row.';');
  478: }
  479: 
  480: # ------------------------------------------------------------------ Get maxrow
  481: 
  482: sub getmaxrow {
  483:     my $safeeval=shift;
  484:     return $safeeval->reval('$maxrow');
  485: }
  486: 
  487: # ---------------------------------------------------------------- Set filename
  488: 
  489: sub setfilename {
  490:     my ($safeeval,$fn)=@_;
  491:     $safeeval->reval('$filename="'.$fn.'";');
  492: }
  493: 
  494: # ---------------------------------------------------------------- Get filename
  495: 
  496: sub getfilename {
  497:     my $safeeval=shift;
  498:     return $safeeval->reval('$filename');
  499: }
  500: 
  501: # ----------------------------------------------------------- Get course number
  502: 
  503: sub getcnum {
  504:     my $safeeval=shift;
  505:     return $safeeval->reval('$cnum');
  506: }
  507: 
  508: # ------------------------------------------------------------- Get course home
  509: 
  510: sub getchome {
  511:     my $safeeval=shift;
  512:     return $safeeval->reval('$chome');
  513: }
  514: 
  515: # ----------------------------------------------------------- Get course domain
  516: 
  517: sub getcdom {
  518:     my $safeeval=shift;
  519:     return $safeeval->reval('$cdom');
  520: }
  521: 
  522: # ---------------------------------------------------------- Get course section
  523: 
  524: sub getcsec {
  525:     my $safeeval=shift;
  526:     return $safeeval->reval('$csec');
  527: }
  528: 
  529: # --------------------------------------------------------------- Get user name
  530: 
  531: sub getuname {
  532:     my $safeeval=shift;
  533:     return $safeeval->reval('$uname');
  534: }
  535: 
  536: # ------------------------------------------------------------- Get user domain
  537: 
  538: sub getudom {
  539:     my $safeeval=shift;
  540:     return $safeeval->reval('$udom');
  541: }
  542: 
  543: # --------------------------------------------------------------- Get user home
  544: 
  545: sub getuhome {
  546:     my $safeeval=shift;
  547:     return $safeeval->reval('$uhome');
  548: }
  549: 
  550: # -------------------------------------------------------------------- Get symb
  551: 
  552: sub getusymb {
  553:     my $safeeval=shift;
  554:     return $safeeval->reval('$usymb');
  555: }
  556: 
  557: # ------------------------------------------------------------- Export of A-row
  558: 
  559: sub exportrow {
  560:     my $safeeval=shift;
  561:     return $safeeval->reval('&exportrowa()');
  562: }
  563: 
  564: # ========================================================== End of Spreadsheet
  565: # =============================================================================
  566: 
  567: #
  568: # Procedures for screen output
  569: #
  570: # --------------------------------------------- Produce output row n from sheet
  571: 
  572: sub rown {
  573:     my ($safeeval,$n)=@_;
  574:     my $defaultbg;
  575:     my $rowdata='';
  576:     unless ($n eq '-') {
  577:        $defaultbg=((($n-1)/5)==int(($n-1)/5))?'#E0E0':'#FFFF';
  578:     } else {
  579:        $defaultbg='#E0FF';
  580:     }
  581:     if ((($n-1)/25)==int(($n-1)/25)) {
  582:         my $what='Student';
  583:         if (&gettype($safeeval) eq 'assesscalc') {
  584: 	    $what='Item';
  585: 	} elsif (&gettype($safeeval) eq 'studentcalc') {
  586:             $what='Assessment';
  587:         }
  588: 	$rowdata.="</table>\n<br><table border=2>".
  589:         '<tr><td>&nbsp;<td>'.$what.'</td>';
  590:         map {
  591:            $rowdata.='<td>'.$_.'</td>';
  592:         } ('A','B','C','D','E','F','G','H','I','J','K','L','M',
  593:            'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
  594:            'a','b','c','d','e','f','g','h','i','j','k','l','m',
  595:            'n','o','p','q','r','s','t','u','v','w','x','y','z');
  596:         $rowdata.='</tr>';
  597:     }
  598:     $rowdata.="\n<tr><td><b><font size=+1>$n</font></b></td>";
  599:     my $showf=0;
  600:     my $proc;
  601:     my $maxred;
  602:     if (&gettype($safeeval) eq 'assesscalc') {
  603:         $proc='&outrowassess';
  604:         $maxred=1;
  605:     } else {
  606:         $proc='&outrow';
  607:         $maxred=26;
  608:     }
  609:     if ($n eq '-') { $proc='&templaterow'; $n=-1; }
  610:     map {
  611:        my $bgcolor=$defaultbg.((($showf-1)/5==int(($showf-1)/5))?'99':'DD');
  612:        my ($fm,$vl)=split(/\_\_\_eq\_\_\_/,$_);
  613:        if ($showf==0) { $vl=$_; }
  614:        if ($showf<=$maxred) { $bgcolor='#FFDDDD'; }
  615:        if (($n==0) && ($showf<=26)) { $bgcolor='#CCCCFF'; } 
  616:        if (($showf>$maxred) || ((!$n) && ($showf>0))) {
  617: 	   if ($vl eq '') {
  618: 	       $vl='<font size=+2 color='.$bgcolor.'>&#35;</font>';
  619:            }
  620:            $rowdata.=
  621:        '<td bgcolor='.$bgcolor.'><a href="javascript:celledit('.$fm.');">'.$vl.
  622: 	       '</a></td>';
  623:        } else {
  624:            $rowdata.='<td bgcolor='.$bgcolor.'>&nbsp;'.$vl.'&nbsp;</td>';
  625:        }
  626:        $showf++;
  627:     } $safeeval->reval($proc.'('.$n.')');
  628:     return $rowdata.'</tr>';
  629: }
  630: 
  631: # ------------------------------------------------------------- Print out sheet
  632: 
  633: sub outsheet {
  634:     my ($r,$safeeval)=@_;
  635:     my $maxred;
  636:     my $realm;
  637:     if (&gettype($safeeval) eq 'assesscalc') {
  638:         $maxred=1;
  639:         $realm='Assessment';
  640:     } elsif (&gettype($safeeval) eq 'studentcalc') {
  641:         $maxred=26;
  642:         $realm='User';
  643:     } else {
  644:         $maxred=26;
  645:         $realm='Course';
  646:     }
  647:     my $maxyellow=52-$maxred;
  648:     my $tabledata=
  649:         '<table border=2><tr><th colspan=2 rowspan=2><font size=+2>'.
  650:                   $realm.'</font></th>'.
  651:                   '<td bgcolor=#FFDDDD colspan='.$maxred.
  652:                   '><b><font size=+1>Import</font></b></td>'.
  653:                   '<td colspan='.$maxyellow.
  654: 		  '><b><font size=+1>Calculations</font></b></td></tr><tr>';
  655:     my $showf=0;
  656:     map {
  657:         $showf++;
  658:         if ($showf<=$maxred) { 
  659:            $tabledata.='<td bgcolor="#FFDDDD">'; 
  660:         } else {
  661:            $tabledata.='<td>';
  662:         }
  663:         $tabledata.="<b><font size=+1>$_</font></b></td>";
  664:     } ('A','B','C','D','E','F','G','H','I','J','K','L','M',
  665:        'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
  666:        'a','b','c','d','e','f','g','h','i','j','k','l','m',
  667:        'n','o','p','q','r','s','t','u','v','w','x','y','z');
  668:     $tabledata.='</tr>';
  669:     my $row;
  670:     my $maxrow=&getmaxrow($safeeval);
  671:     $tabledata.=&rown($safeeval,'-');
  672:     $r->print($tabledata);
  673:     for ($row=0;$row<=$maxrow;$row++) {
  674:         $r->print(&rown($safeeval,$row));
  675:     }
  676:     $r->print('</table>');
  677: }
  678: 
  679: #
  680: # -------------------------------------- Read spreadsheet formulas for a course
  681: #
  682: 
  683: sub readsheet {
  684:   my ($safeeval,$fn)=@_;
  685:   my $stype=&gettype($safeeval);
  686:   my $cnum=&getcnum($safeeval);
  687: 
  688: # --------- There is no filename. Look for defaults in course and global, cache
  689: 
  690:   unless($fn) {
  691:       unless ($fn=$defaultsheets{$cnum.'_'.$stype}) {
  692:          $fn=&Apache::lonnet::reply('get:'.
  693:                 $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.':'.
  694:                 $ENV{'course.'.$ENV{'request.course.id'}.'.num'}.
  695:                 ':environment:spreadsheet_default_'.&gettype($safeeval),
  696:                      $ENV{'course.'.$ENV{'request.course.id'}.'.home'});
  697:          unless (($fn) && ($fn!~/^error\:/)) {
  698: 	     $fn='default_'.$stype;
  699:          }
  700:          $defaultsheets{$cnum.'_'.$stype}=$fn; 
  701:       }
  702:   }
  703: 
  704: # ---------------------------------------------------------- fn now has a value
  705: 
  706:   &setfilename($safeeval,$fn);
  707: 
  708: # ------------------------------------------------------ see if sheet is cached
  709:   my $fstring='';
  710:   if ($fstring=$spreadsheets{$cnum.'_'.$stype.'_'.$fn}) {
  711:       &setformulas($sheetone,split(/\_\_\_\;\_\_\_/,$fstring));
  712:   } else {
  713: 
  714: # ---------------------------------------------------- Not cached, need to read
  715: 
  716:      my %f=();
  717: 
  718:      if ($fn=~/^default\_/) {
  719: 	my $sheetxml='';
  720:        {
  721:          my $fh;
  722:          if ($fh=Apache::File->new($r->dir_config('lonIncludes').
  723:                          '/default.'.&gettype($safeeval))) {
  724:                $sheetxml=join('',<$fh>);
  725:           }
  726:        }
  727:         my $parser=HTML::TokeParser->new(\$sheetxml);
  728:         my $token;
  729:         while ($token=$parser->get_token) {
  730:           if ($token->[0] eq 'S') {
  731:  	     if ($token->[1] eq 'field') {
  732:  		 $f{$token->[2]->{'col'}.$token->[2]->{'row'}}=
  733:  		     $parser->get_text('/field');
  734:  	     }
  735:              if ($token->[1] eq 'template') {
  736:                  $f{'template_'.$token->[2]->{'col'}}=
  737:                      $parser->get_text('/template');
  738:              }
  739:           }
  740:         }
  741:       } else {
  742:         my $sheet='';
  743:         my $reply=&Apache::lonnet::reply('dump:'.
  744:               $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.':'.
  745:               $ENV{'course.'.$ENV{'request.course.id'}.'.num'}.':'.$fn,
  746:               $ENV{'course.'.$ENV{'request.course.id'}.'.home'});
  747:           unless ($reply=~/^error\:/) {
  748:              $sheet=$reply;
  749: 	  }
  750:           map {
  751:              my ($name,$value)=split(/\=/,$_);
  752:              $f{&Apache::lonnet::unescape($name)}=
  753: 	        &Apache::lonnet::unescape($value);
  754:           } split(/\&/,$sheet);
  755:        }
  756: # --------------------------------------------------------------- Cache and set
  757:        $spreadsheets{$cnum.'_'.$stype.'_'.$fn}=join('___;___',%f);       
  758:        &setformulas($safeeval,%f);
  759:     }
  760: }
  761: 
  762: # ------------------------------------------------------------ Save spreadsheet
  763: 
  764: sub writesheet {
  765:   my $safeeval=shift;
  766:   if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {
  767:     my %f=&getformulas($safeeval);
  768:     my $sheetdata='';
  769:     map {
  770:        $sheetdata.=&Apache::lonnet::escape($_).'='.
  771: 	   &Apache::lonnet::escape($f{$_}).'&';
  772:     } keys %f;
  773:     $sheetdata=~s/\&$//;
  774:     my $reply=&Apache::lonnet::reply('put:'.
  775:               $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.':'.
  776:               $ENV{'course.'.$ENV{'request.course.id'}.'.num'}.':'.
  777:               &getfilename($safeeval).':'.
  778:               $sheetdata,
  779:               $ENV{'course.'.$ENV{'request.course.id'}.'.home'});
  780:     if ($reply eq 'ok') {
  781:           return &Apache::lonnet::reply('put:'.
  782:               $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.':'.
  783:               $ENV{'course.'.$ENV{'request.course.id'}.'.num'}.':'.
  784:               &gettype($safeeval).'_spreadsheets:'.
  785:               &Apache::lonnet::escape(&getfilename($safeeval)).'='.
  786:               $ENV{'user.name'},
  787:               $ENV{'course.'.$ENV{'request.course.id'}.'.home'});    
  788:       } else {
  789: 	  return $reply;
  790:       }
  791:   }
  792:   return 'unauthorized';
  793: }
  794: 
  795: # ----------------------------------------------- Make a temp copy of the sheet
  796: 
  797: sub tmpwrite {
  798:     my ($safeeval,$tmpdir,$symb)=@_;
  799:     my $fn=$uname.'_'.$udom.'_spreadsheet_'.$symb.'_'.&getfilename($safeeval);
  800:     $fn=~s/\W/\_/g;
  801:     $fn=$tmpdir.$fn.'.tmp';
  802:     my $fh;
  803:     if ($fh=Apache::File->new('>'.$fn)) {
  804: 	print $fh join("\n",&getformulas($safeeval));
  805:     }
  806: }
  807: 
  808: # ---------------------------------------------------------- Read the temp copy
  809: 
  810: sub tmpread {
  811:     my ($safeeval,$tmpdir,$symb,$nfield,$nform)=@_;
  812:     my $fn=$uname.'_'.$udom.'_spreadsheet_'.$symb.'_'.&getfilename($safeeval);
  813:     $fn=~s/\W/\_/g;
  814:     $fn=$tmpdir.$fn.'.tmp';
  815:     my $fh;
  816:     my %fo=();
  817:     if ($fh=Apache::File->new($fn)) {
  818:         my $name;
  819:         while ($name=<$fh>) {
  820: 	    chomp($name);
  821:             my $value=<$fh>;
  822:             chomp($value);
  823:             $fo{$name}=$value;
  824:         }
  825:     }
  826:     if ($nfield) { $fo{$nfield}=$nform; }
  827:     &setformulas($safeeval,%fo);
  828: }
  829: 
  830: # --------------------------------------------------------------- Read metadata
  831: 
  832: sub readmeta {
  833:     my $fn=shift;
  834:     unless ($fn=~/\.meta$/) { $fn.='meta'; }
  835:     my $content;
  836:     my %returnhash=();
  837:     {
  838:       my $fh=Apache::File->new($fn);
  839:       $content=join('',<$fh>);
  840:     }
  841:    my $parser=HTML::TokeParser->new(\$content);
  842:    my $token;
  843:    while ($token=$parser->get_token) {
  844:       if ($token->[0] eq 'S') {
  845:          my $entry=$token->[1];
  846:          if (($entry eq 'stores') || ($entry eq 'parameter')) {
  847:              my $unikey=$entry;
  848:              $unikey.='_'.$token->[2]->{'part'}; 
  849:              $unikey.='_'.$token->[2]->{'name'}; 
  850:              $returnhash{$unikey}=$token->[2]->{'display'};
  851:          }
  852:      }
  853:   }
  854:     return %returnhash;
  855: }
  856: 
  857: # ================================================================== Parameters
  858: # -------------------------------------------- Figure out a cascading parameter
  859: 
  860: sub parmval {
  861:     my ($what,$symb)=@_;
  862: 
  863:     unless ($symb) { return ''; }
  864:     my $result='';
  865: 
  866:     my ($mapname,$id,$fn)=split(/\_\_\_/,$symb);
  867: # ----------------------------------------------------- Cascading lookup scheme
  868:        my $rwhat=$what;
  869:        $what=~s/^parameter\_//;
  870:        $what=~s/\_/\./;
  871: 
  872:        my $symbparm=$symb.'.'.$what;
  873:        my $mapparm=$mapname.'___(all).'.$what;
  874: 
  875:        my $seclevel=
  876:             $ENV{'request.course.id'}.'.['.
  877: 		$csec.'].'.$what;
  878:        my $seclevelr=
  879:             $ENV{'request.course.id'}.'.['.
  880: 		$csec.'].'.$symbparm;
  881:        my $seclevelm=
  882:             $ENV{'request.course.id'}.'.['.
  883: 		$csec.'].'.$mapparm;
  884: 
  885:        my $courselevel=
  886:             $ENV{'request.course.id'}.'.'.$what;
  887:        my $courselevelr=
  888:             $ENV{'request.course.id'}.'.'.$symbparm;
  889:        my $courselevelm=
  890:             $ENV{'request.course.id'}.'.'.$mapparm;
  891: 
  892: # ---------------------------------------------------------- fourth, check user
  893:       
  894:       if ($uname) { 
  895: 
  896:        if ($useropt{$courselevelr}) { return $useropt{$courselevelr}; }
  897: 
  898:        if ($useropt{$courselevelm}) { return $useropt{$courselevelm}; }
  899: 
  900:        if ($useropt{$courselevel}) { return $useropt{$courselevel}; }
  901: 
  902:       }
  903: 
  904: # --------------------------------------------------------- third, check course
  905:      
  906:        if ($csec) {
  907:  
  908:         if ($courseopt{$seclevelr}) { return $courseopt{$seclevelr}; }
  909: 
  910:         if ($courseopt{$seclevelm}) { return $courseopt{$seclevelm}; }  
  911: 
  912:         if ($courseopt{$seclevel}) { return $courseopt{$seclevel}; }
  913:   
  914:       }
  915: 
  916:        if ($courseopt{$courselevelr}) { return $courseopt{$courselevelr}; }
  917: 
  918:        if ($courseopt{$courselevelm}) { return $courseopt{$courselevelm}; }
  919: 
  920:        if ($courseopt{$courselevel}) { return $courseopt{$courselevel}; }
  921: 
  922: # ----------------------------------------------------- second, check map parms
  923: 
  924:        my $thisparm=$parmhash{$symbparm};
  925:        if ($thisparm) { return $thisparm; }
  926: 
  927: # -------------------------------------------------------- first, check default
  928: 
  929:        return &Apache::lonnet::metadata($fn,$rwhat.'.default');
  930:         
  931: }
  932: 
  933: # ---------------------------------------------- Update rows for course listing
  934: 
  935: sub updatestudentrows {
  936:     my $safeeval=shift;
  937:     my $cid=$ENV{'request.course.id'};
  938:     my $classlst=&Apache::lonnet::reply
  939:                  ('dump:'.$ENV{'course.'.$cid.'.domain'}.':'.
  940: 	                  $ENV{'course.'.$cid.'.num'}.':classlist',
  941: 	                  $ENV{'course.'.$cid.'.home'});
  942:     my %currentlist=();
  943:     my $now=time;
  944:     unless ($classlst=~/^error\:/) {
  945:         map {
  946:             my ($name,$value)=split(/\=/,$_);
  947:             my ($end,$start)=split(/\:/,&Apache::lonnet::unescape($value));
  948:             my $active=1;
  949:             if (($end) && ($now>$end)) { $active=0; }
  950:             if ($active) {
  951:                 my $rowlabel='';
  952:                 $name=&Apache::lonnet::unescape($name);
  953:                 my ($cname,$cdom)=split(/\:/,$name);
  954:                 my $csec=
  955:              &Apache::lonnet::usection($cdom,$cname,$ENV{'request.course.id'});
  956:                 if ($csec==-1) {
  957:                     $rowlabel='<font color=red>Data not available: '.$name.
  958: 			      '</font>';
  959:                 } else {
  960:                     my %reply=&Apache::lonnet::idrget($cdom,$cname);
  961:                     my $reply=&Apache::lonnet::reply('get:'.$cdom.':'.$cname.
  962: 		      ':environment:firstname&middlename&lastname&generation',
  963:                       &Apache::lonnet::homeserver($cname,$cdom));
  964:                     $rowlabel=$csec.'&nbsp;'.$reply{$cname}.'<br>';
  965:                     map {
  966:                         $rowlabel.=&Apache::lonnet::unescape($_).' ';
  967:                     } split(/\&/,$reply);
  968:                 }
  969:                
  970: 		$currentlist{&Apache::lonnet::unescape($name)}=$rowlabel;
  971:             }
  972:         } split(/\&/,$classlst);
  973: #
  974: # -------------------- Find discrepancies between the course row table and this
  975: #
  976:         my %f=&getformulas($safeeval);
  977:         my $changed=0;
  978: 
  979:         my $maxrow=0;
  980:         my %existing=();
  981: 
  982: # ----------------------------------------------------------- Now obsolete rows
  983: 	map {
  984: 	    if ($_=~/^A(\d+)/) {
  985:                 $maxrow=($1>$maxrow)?$1:$maxrow;
  986:                 $existing{$f{$_}}=1;
  987: 		unless ((defined($currentlist{$f{$_}})) || (!$1)) {
  988: 		   $f{$_}='!!! Obsolete';
  989:                    $changed=1;
  990:                 }
  991:             }
  992:         } keys %f;
  993: 
  994: # -------------------------------------------------------- New and unknown keys
  995:      
  996:         map {
  997:             unless ($existing{$_}) {
  998: 		$changed=1;
  999:                 $maxrow++;
 1000:                 $f{'A'.$maxrow}=$_;
 1001:             }
 1002:         } sort keys %currentlist;        
 1003:      
 1004:         if ($changed) { &setformulas($safeeval,%f); }
 1005: 
 1006:         &setmaxrow($safeeval,$maxrow);
 1007:         &setrowlabels($safeeval,%currentlist);
 1008: 
 1009:     } else {
 1010:         return 'Could not access course data';
 1011:     }
 1012: }
 1013: # ----------------------------------------------------------------- Update rows
 1014: 
 1015: sub updaterows {
 1016:     my $safeeval=shift;
 1017:     my %bighash;
 1018: # -------------------------------------------------------------------- Tie hash
 1019:       if (tie(%bighash,'GDBM_File',$ENV{'request.course.fn'}.'.db',
 1020:                        &GDBM_READER,0640)) {
 1021: # --------------------------------------------------------- Get all assessments
 1022: 
 1023: 	my %allkeys=();
 1024:         my %allassess=();
 1025: 
 1026:         my $stype=&gettype($safeeval);
 1027: 
 1028:         map {
 1029: 	    if ($_=~/^src\_(\d+)\.(\d+)$/) {
 1030: 	       my $mapid=$1;
 1031:                my $resid=$2;
 1032:                my $id=$mapid.'.'.$resid;
 1033:                my $srcf=$bighash{$_};
 1034:                if ($srcf=~/\.(problem|exam|quiz|assess|survey|form)$/) {
 1035:                  my $symb=
 1036:                      &Apache::lonnet::declutter($bighash{'map_id_'.$mapid}).
 1037: 			    '___'.$resid.'___'.
 1038: 			    &Apache::lonnet::declutter($srcf);
 1039: 		 $allassess{$symb}=$bighash{'title_'.$id};
 1040: 
 1041:                  if ($stype eq 'assesscalc') {
 1042:                    map {
 1043:                        if (($_=~/^stores\_(.*)/) || ($_=~/^parameter\_(.*)/)) {
 1044: 			  my $key=$_;
 1045:                           my $display=
 1046: 			      &Apache::lonnet::metadata($srcf,$key.'.display');
 1047:                           unless ($display) {
 1048:                               $display=
 1049: 			         &Apache::lonnet::metadata($srcf,$key.'.name');
 1050:                           }
 1051:                           $allkeys{$key}=$display;
 1052: 		       }
 1053:                    } split(/\,/,&Apache::lonnet::metadata($srcf,'keys'));
 1054: 	         }
 1055: 	      }
 1056: 	   }
 1057:         } keys %bighash;
 1058:         untie(%bighash);
 1059:     
 1060: #
 1061: # %allkeys has a list of storage and parameter displays by unikey
 1062: # %allassess has a list of all resource displays by symb
 1063: #
 1064: # -------------------- Find discrepancies between the course row table and this
 1065: #
 1066:         my %f=&getformulas($safeeval);
 1067:         my $changed=0;
 1068: 
 1069:         my %current=();
 1070:         if ($stype eq 'assesscalc') {
 1071: 	    %current=%allkeys;
 1072:         } elsif ($stype eq 'studentcalc') {
 1073:             %current=%allassess;
 1074:         }
 1075: 
 1076:         my $maxrow=0;
 1077:         my %existing=();
 1078: 
 1079: # ----------------------------------------------------------- Now obsolete rows
 1080: 	map {
 1081: 	    if ($_=~/^A(\d+)/) {
 1082:                 $maxrow=($1>$maxrow)?$1:$maxrow;
 1083:                 $existing{$f{$_}}=1;
 1084: 		unless ((defined($current{$f{$_}})) || (!$1)) {
 1085: 		   $f{$_}='!!! Obsolete';
 1086:                    $changed=1;
 1087:                 }
 1088:             }
 1089:         } keys %f;
 1090: 
 1091: # -------------------------------------------------------- New and unknown keys
 1092:      
 1093:         map {
 1094:             unless ($existing{$_}) {
 1095: 		$changed=1;
 1096:                 $maxrow++;
 1097:                 $f{'A'.$maxrow}=$_;
 1098:             }
 1099:         } keys %current;        
 1100:      
 1101:         if ($changed) { &setformulas($safeeval,%f); }
 1102: 
 1103:         &setmaxrow($safeeval,$maxrow);
 1104:         &setrowlabels($safeeval,%current);
 1105: 
 1106:     } else {
 1107:         return 'Could not access course data';
 1108:     }
 1109: }
 1110: 
 1111: # ------------------------------------------------ Load data for one assessment
 1112: 
 1113: sub rowazstudent {
 1114:     my $safeeval=shift;
 1115:     my %c=();
 1116:     my %f=&getformulas($safeeval);
 1117:     map {
 1118: 	if ($_=~/^A(\d+)/) {
 1119: 	   my $row=$1;
 1120:            unless ($f{$_}=~/^\!/) {
 1121:               my @assessdata=split(/\_\_\_\;\_\_\_/,
 1122:                              &Apache::lonnet::ssi(
 1123:                        '/adm/assesscalc',('utarget' => 'export',
 1124:                                           'uname'   => $uname,
 1125:                                           'udom'    => $udom,
 1126: 			                  'usymb'   => $f{$_})));
 1127:               my $index=0;
 1128:               map {
 1129:                   if ($assessdata[$index]) {
 1130: 		     $c{$_.$row}=$assessdata[$index];
 1131:                      unless ($_ eq 'A') { 
 1132: 			 $f{$_.$row}='import';
 1133:                      }
 1134: 		  }
 1135:                   $index++;
 1136:               } ('A','B','C','D','E','F','G','H','I','J','K','L','M',
 1137:                  'N','O','P','Q','R','S','T','U','V','W','X','Y','Z');
 1138: 	   }
 1139:         }
 1140:     } keys %f;
 1141:     &setformulas($safeeval,%f);
 1142:     &setconstants($safeeval,%c);
 1143: }
 1144: 
 1145: # --------------------------------------------------- Load data for one student
 1146: 
 1147: sub rowazclass {
 1148:     my $safeeval=shift;
 1149:     my %c=();
 1150:     my %f=&getformulas($safeeval);
 1151:     map {
 1152: 	if ($_=~/^A(\d+)/) {
 1153: 	   my $row=$1;
 1154:            unless ($f{$_}=~/^\!/) {
 1155: 	      my ($tname,$tdom)=split(/\:/,$_);
 1156:               my @assessdata=split(/\_\_\_\;\_\_\_/,
 1157:                              &Apache::lonnet::ssi(
 1158:                       '/adm/studentcalc',('utarget' => 'export',
 1159:                                           'uname'   => $tname,
 1160:                                           'udom'    => $tdom)));
 1161:               my $index=0;
 1162:               map {
 1163:                   if ($assessdata[$index]) {
 1164: 		     $c{$_.$row}=$assessdata[$index];
 1165:                      unless ($_ eq 'A') { 
 1166: 			 $f{$_.$row}='import';
 1167:                      }
 1168: 		  }
 1169:                   $index++;
 1170:               } ('A','B','C','D','E','F','G','H','I','J','K','L','M',
 1171:                  'N','O','P','Q','R','S','T','U','V','W','X','Y','Z');
 1172: 	   }
 1173:         }
 1174:     } keys %f;
 1175:     &setformulas($safeeval,%f);
 1176:     &setconstants($safeeval,%c);
 1177: }
 1178: 
 1179: # ------------------------------------------------ Load data for one assessment
 1180: 
 1181: sub rowaassess {
 1182:     my ($safeeval,$symb)=@_;
 1183:     my $uhome=&Apache::lonnet::homeserver($uname,$udom);
 1184:     my $namespace;
 1185:     unless ($namespace=$ENV{'request.course.id'}) { return ''; }
 1186: 
 1187: # ----------------------------------------------------------- Get stored values
 1188:     my $answer=&Apache::lonnet::reply(
 1189:        "restore:$udom:$uname:".
 1190:        &Apache::lonnet::escape($namespace).":".
 1191:        &Apache::lonnet::escape($symb),$uhome);
 1192:     my %returnhash=();
 1193:     map {
 1194: 	my ($name,$value)=split(/\=/,$_);
 1195:         $returnhash{&Apache::lonnet::unescape($name)}=
 1196:                     &Apache::lonnet::unescape($value);
 1197:     } split(/\&/,$answer);
 1198:     my $version;
 1199:     for ($version=1;$version<=$returnhash{'version'};$version++) {
 1200:        map {
 1201:           $returnhash{$_}=$returnhash{$version.':'.$_};
 1202:        } split(/\:/,$returnhash{$version.':keys'});
 1203:     }
 1204: # ----------------------------- returnhash now has all stores for this resource
 1205: 
 1206: # ---------------------------- initialize coursedata and userdata for this user
 1207:     %courseopt=();
 1208:     %useropt=();
 1209:     my $uhome=&Apache::lonnet::homeserver($uname,$udom);
 1210:     unless ($uhome eq 'no_host') { 
 1211: # -------------------------------------------------------------- Get coursedata
 1212:       unless
 1213:         ((time-$courserdatas{$ENV{'request.course.id'}.'.last_cache'})<120) {
 1214:          my $reply=&Apache::lonnet::reply('dump:'.
 1215:               $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.':'.
 1216:               $ENV{'course.'.$ENV{'request.course.id'}.'.num'}.':resourcedata',
 1217:               $ENV{'course.'.$ENV{'request.course.id'}.'.home'});
 1218:          if ($reply!~/^error\:/) {
 1219:             $courserdatas{$ENV{'request.course.id'}}=$reply;
 1220:             $courserdatas{$ENV{'request.course.id'}.'.last_cache'}=time;
 1221:          }
 1222:       }
 1223:       map {
 1224:          my ($name,$value)=split(/\=/,$_);
 1225:          $courseopt{&Apache::lonnet::unescape($name)}=
 1226:                     &Apache::lonnet::unescape($value);  
 1227:       } split(/\&/,$courserdatas{$ENV{'request.course.id'}});
 1228: # --------------------------------------------------- Get userdata (if present)
 1229:       unless
 1230:         ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<120) {
 1231:          my $reply=
 1232:        &Apache::lonnet::reply('dump:'.$udom.':'.$uname.':resourcedata',$uhome);
 1233:          if ($reply!~/^error\:/) {
 1234: 	     $userrdatas{$uname.'___'.$udom}=$reply;
 1235: 	     $userrdatas{$uname.'___'.$udom.'.last_cache'}=time;
 1236:          }
 1237:       }
 1238:       map {
 1239:          my ($name,$value)=split(/\=/,$_);
 1240:          $useropt{&Apache::lonnet::unescape($name)}=
 1241: 	          &Apache::lonnet::unescape($value);
 1242:       } split(/\&/,$userrdatas{$uname.'___'.$udom});
 1243:    }
 1244: # -- now courseopt, useropt initialized for this user and course (used parmval)
 1245: 
 1246:     my %c=();
 1247:     my %f=&getformulas($safeeval);
 1248:     map {
 1249: 	if ($_=~/^A/) {
 1250:             unless ($f{$_}=~/^\!/) {
 1251:   	       if ($f{$_}=~/^parameter/) {
 1252: 	          $c{$_}=&parmval($f{$_},$symb);
 1253: 	       } else {
 1254: 		  my $key=$f{$_};
 1255:                   $key=~s/^stores\_/resource\./;
 1256:                   $key=~s/\_/\./;
 1257:  	          $c{$_}=$returnhash{$key};
 1258: 	       }
 1259: 	   }
 1260:         }
 1261:     } keys %f;
 1262: 
 1263:     &setconstants($safeeval,%c);
 1264: }
 1265: 
 1266: # --------------------------------------------------------- Various form fields
 1267: 
 1268: sub textfield {
 1269:     my ($title,$name,$value)=@_;
 1270:     return "\n<p><b>$title:</b><br>".
 1271:            '<input type=text name="'.$name.'" size=80 value="'.$value.'">';
 1272: }
 1273: 
 1274: sub hiddenfield {
 1275:     my ($name,$value)=@_;
 1276:     return "\n".'<input type=hidden name="'.$name.'" value="'.$value.'">';
 1277: }
 1278: 
 1279: sub selectbox {
 1280:     my ($title,$name,$value,%options)=@_;
 1281:     my $selout="\n<p><b>$title:</b><br>".'<select name="'.$name.'">';
 1282:     map {
 1283:         $selout.='<option value="'.$_.'"';
 1284:         if ($_ eq $value) { $selout.=' selected'; }
 1285:         $selout.='>'.$options{$_}.'</option>';
 1286:     } sort keys %options;
 1287:     return $selout.'</select>';
 1288: }
 1289: 
 1290: # ================================================================ Main handler
 1291: 
 1292: sub handler {
 1293:     my $r=shift;
 1294: 
 1295:     $uname='';
 1296:     $udom='';
 1297:     $csec='';
 1298: 
 1299:    if ($r->header_only) {
 1300:       $r->content_type('text/html');
 1301:       $r->send_http_header;
 1302:       return OK;
 1303:    }
 1304: 
 1305: # ----------------------------------------------------- Needs to be in a course
 1306: 
 1307:   if (($ENV{'request.course.fn'}) || 
 1308:       ($ENV{'request.state'} eq 'construct')) { 
 1309: 
 1310: # --------------------------- Get query string for limited number of parameters
 1311: 
 1312:     map {
 1313:        my ($name, $value) = split(/=/,$_);
 1314:        $value =~ tr/+/ /;
 1315:        $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
 1316:        if (($name eq 'uname') || ($name eq 'udom') || 
 1317:            ($name eq 'usymb') || ($name eq 'ufn')) {
 1318:            unless ($ENV{'form.'.$name}) {
 1319:               $ENV{'form.'.$name}=$value;
 1320: 	   }
 1321:        }
 1322:     } (split(/&/,$ENV{'QUERY_STRING'}));
 1323: 
 1324: # ------------------------------------------- Nothing there? Must be login user
 1325:     unless ($ENV{'form.uname'}) {
 1326: 	$uname=$ENV{'user.name'};
 1327:         $udom=$ENV{'user.domain'};
 1328:     } else {
 1329:         $uname=$ENV{'form.uname'};
 1330:         $udom=$ENV{'form.udom'};
 1331:     }
 1332: # ----------------------------------------------------------- Change of target?
 1333: 
 1334:     my $reroute=($ENV{'form.utarget'} eq 'export');
 1335: 
 1336: # ------------------------------------------------------------------- Open page
 1337: 
 1338:     $r->content_type('text/html');
 1339:     $r->header_out('Cache-control','no-cache');
 1340:     $r->header_out('Pragma','no-cache');
 1341:     $r->send_http_header;
 1342: 
 1343: # --------------------------------------------------------------- Screen output
 1344: 
 1345:   unless ($reroute) {
 1346:     $r->print('<html><head><title>LON-CAPA Spreadsheet</title>');
 1347:     $r->print(<<ENDSCRIPT);
 1348: <script language="JavaScript">
 1349: 
 1350:     function celledit(cn,cf) {
 1351:         var cnf=prompt(cn,cf);
 1352: 	if (cnf!=null) {
 1353: 	    document.sheet.unewfield.value=cn;
 1354:             document.sheet.unewformula.value=cnf;
 1355:             document.sheet.submit();
 1356:         }
 1357:     }
 1358: 
 1359: </script>
 1360: ENDSCRIPT
 1361:     $r->print('</head><body bgcolor="#FFFFFF">'.
 1362:        '<img align=right src=/adm/lonIcons/lonlogos.gif>'.
 1363:        '<h1>LON-CAPA Spreadsheet</h1>'.
 1364:        '<form action="'.$r->uri.'" name=sheet method=post>'.
 1365:        &hiddenfield('uname',$ENV{'form.uname'}).
 1366:        &hiddenfield('udom',$ENV{'form.udom'}).
 1367:        &hiddenfield('usymb',$ENV{'form.usymb'}).
 1368:        &hiddenfield('unewfield','').
 1369:        &hiddenfield('unewformula',''));
 1370:   }
 1371:     $r->rflush();
 1372: # ---------------------------------------- Read new sheet or modified worksheet
 1373: 
 1374:     my $sheetone=initsheet();
 1375:     $r->uri=~/\/(\w+)$/;
 1376:     &settype($sheetone,$1);
 1377:     if ($ENV{'form.unewfield'}) {
 1378:         $r->print('<h2>Modified Workcopy</h2>');
 1379:         $ENV{'form.unewformula'}=~s/\'/\"/g;
 1380:         $r->print('<p>New formula: '.$ENV{'form.unewfield'}.'='.
 1381:                   $ENV{'form.unewformula'}.'<p>');
 1382:         &setfilename($sheetone,$ENV{'form.ufn'});
 1383: 	&tmpread($sheetone,$r->dir_config('lonDaemons').'/tmp/',
 1384:                  $ENV{'form.usymb'},
 1385:                  $ENV{'form.unewfield'},$ENV{'form.unewformula'});
 1386:     } elsif ($ENV{'form.saveas'}) {
 1387:         &setfilename($sheetone,$ENV{'form.ufn'});
 1388: 	&tmpread($sheetone,$r->dir_config('lonDaemons').'/tmp/',
 1389:                  $ENV{'form.usymb'});
 1390:     } else {
 1391:         unless ($ENV{'form.ufn'}) {
 1392:     }
 1393: 
 1394:   if (&gettype($sheetone) eq 'classcalc') {
 1395: # ---------------------------------- For course view: get courselist and update
 1396:        &updatestudentrows($sheetone);
 1397:   } else {
 1398: # ----------------- For assessment and student: See if all import rows uptodate
 1399: 
 1400:     if (tie(%parmhash,'GDBM_File',
 1401:        $ENV{'request.course.fn'}.'_parms.db',&GDBM_READER,0640)) {
 1402:        $csec=&Apache::lonnet::usection($udom,$uname,$ENV{'request.course.id'});
 1403:        if ($csec eq '-1') {
 1404:           $r->print('<h3><font color=red>'.
 1405:    "User '$uname' at domain '$udom' not a student in this course</font></h3>");
 1406:        }
 1407:        &updaterows($sheetone);
 1408:        untie(%parmhash);
 1409:    } else {
 1410:        $r->print('<h3><font color=red>'.
 1411: 	   'Could not initialize import fields (not in a course)</font></h3>');
 1412:    }
 1413:  }
 1414: # ---------------------------------------------------- See if something to save
 1415:     if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {
 1416:         my $fname='';
 1417: 	if ($ENV{'form.saveas'} && ($fname=$ENV{'form.newfn'})) {
 1418:             $fname=~s/\W/\_/g;
 1419:             if ($fname eq 'default') { $fname='course_default'; }
 1420:             $fname.='_'.&gettype($sheetone);
 1421:             &setfilename($sheetone,$fname);
 1422:             $ENV{'form.ufn'}=$fname;
 1423:             my $reply=&writesheet($sheetone);
 1424:             unless ($reroute) {
 1425: 		$r->print('<p>Saving spreadsheet: '.$reply.'<p>');
 1426:             }
 1427:             if ($ENV{'form.makedefufn'}) {
 1428:                 my $reply=&Apache::lonnet::reply('put:'.
 1429:                      $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.':'.
 1430:                      $ENV{'course.'.$ENV{'request.course.id'}.'.num'}.
 1431:                      ':environment:spreadsheet_default_'.
 1432:                      &gettype($sheetone).'='.
 1433:                      &Apache::lonnet::escape($fname),
 1434:                      $ENV{'course.'.$ENV{'request.course.id'}.'.home'});
 1435:                unless ($reroute) {
 1436: 	           $r->print('<p>Making default spreadsheet: '.$reply.'<p>');
 1437:                }
 1438:             }
 1439:         }
 1440:     }
 1441: # ------------------------------------------------ Write the modified worksheet
 1442: 
 1443:    &tmpwrite($sheetone,$r->dir_config('lonDaemons').'/tmp/',
 1444:               $ENV{'form.usymb'});
 1445: 
 1446: # ----------------------------------------------------- Print user, course, etc
 1447:    unless ($reroute) {
 1448:     if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {
 1449:         my $fname=$ENV{'form.ufn'};
 1450:         $fname=~s/\_[^\_]+$//;
 1451:         if ($fname eq 'default') { $fname='course_default'; }
 1452:         $r->print('<input type=submit name=saveas value="Save as ...">'.
 1453:               '<input type=text size=20 name=newfn value="'.$fname.
 1454:               '"> (make default: <input type=checkbox name="makedefufn">)<p>');
 1455:     }
 1456:     $r->print(&hiddenfield('ufn',$ENV{'form.ufn'}));
 1457:     unless (&gettype($sheetone) eq 'classcalc') {
 1458:         $r->print('<br><b>User:</b> '.$uname.'<br><b>Domain:</b> '.$udom);
 1459:     }
 1460:     $r->print('<h1>'.
 1461:             $ENV{'course.'.$ENV{'request.course.id'}.'.description'}.'</h1>');
 1462:     if ($csec) {
 1463:        $r->print('<h3>Group/Section: '.$csec.'</h3>');
 1464:     }
 1465:    }
 1466: # -------------------------------------------------------- Import and calculate
 1467: 
 1468:     if (&gettype($sheetone) eq 'assesscalc') {
 1469: 	&rowaassess($sheetone,$ENV{'form.usymb'});
 1470:     } elsif  (&gettype($sheetone) eq 'studentcalc') {
 1471: 	&rowazstudent($sheetone);
 1472:     } else {
 1473:         &rowazclass($sheetone);
 1474:     }
 1475:     my $calcoutput=&calcsheet($sheetone);
 1476:     unless ($reroute) {
 1477:        $r->print('<h3><font color=red>'.$calcoutput.'</h3></font>');
 1478:     }
 1479: 
 1480: # ------------------------------------------------------- Print or export sheet
 1481:    unless ($reroute) {   
 1482:     &outsheet($r,$sheetone);
 1483: 
 1484:     $r->print('</form></body></html>');
 1485:   } else {
 1486:      $r->print(&exportrow($sheetone));
 1487:   }
 1488: # ------------------------------------------------------------------------ Done
 1489:   } else {
 1490: # ----------------------------- Not in a course, or not allowed to modify parms
 1491:       $ENV{'user.error.msg'}=
 1492:         $r->uri.":opa:0:0:Cannot modify spreadsheet";
 1493:       return HTTP_NOT_ACCEPTABLE; 
 1494:   }
 1495:     return OK;
 1496: }
 1497: 
 1498: 1;
 1499: __END__
 1500: 
 1501: 
 1502: 
 1503: 
 1504: 
 1505: 
 1506: 
 1507: 
 1508: 
 1509: 
 1510: 
 1511: 
 1512: 
 1513: 
 1514: 
 1515: 

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>