--- loncom/interface/Attic/lonspreadsheet.pm 2001/11/05 18:22:24 1.73 +++ loncom/interface/Attic/lonspreadsheet.pm 2002/07/04 17:51:32 1.93 @@ -1,3 +1,28 @@ +# +# $Id: lonspreadsheet.pm,v 1.93 2002/07/04 17:51:32 www Exp $ +# +# Copyright Michigan State University Board of Trustees +# +# This file is part of the LearningOnline Network with CAPA (LON-CAPA). +# +# LON-CAPA is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# LON-CAPA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with LON-CAPA; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# /home/httpd/html/adm/gpl.txt +# +# http://www.lon-capa.org/ +# # The LearningOnline Network with CAPA # Spreadsheet/Grades Display Handler # @@ -7,7 +32,34 @@ # 03/05,03/08,03/10,03/12,03/13,03/15,03/17, # 03/19,03/20,03/21,03/27,04/05,04/09, # 07/09,07/14,07/21,09/01,09/10,9/11,9/12,9/13,9/14,9/17, -# 10/16,10/17,10/20,11/05 Gerd Kortemeyer +# 10/16,10/17,10/20,11/05,11/28,12/27 Gerd Kortemeyer +# 01/14/02 Matthew +# 02/04/02 Matthew + +# POD required stuff: + +=head1 NAME + +lonspreadsheet + +=head1 SYNOPSIS + +Spreadsheet interface to internal LON-CAPA data + +=head1 DESCRIPTION + +Lonspreadsheet provides course coordinators the ability to manage their +students grades online. The students are able to view their own grades, but +not the grades of their peers. The spreadsheet is highly customizable, +offering the ability to use Perl code to manipulate data, as well as many +built-in functions. + + +=head2 Functions available to user of lonspreadsheet + +=over 4 + +=cut package Apache::lonspreadsheet; @@ -142,16 +194,16 @@ sub mask { } else { if (length($ld)!=length($ud)) { $num.='('; - map { + foreach ($ld=~m/\d/g) { $num.='['.$_.'-9]'; - } ($ld=~m/\d/g); + } if (length($ud)-length($ld)>1) { $num.='|\d{'.(length($ld)+1).','.(length($ud)-1).'}'; } $num.='|'; - map { + foreach ($ud=~m/\d/g) { $num.='[0-'.$_.']'; - } ($ud=~m/\d/g); + } $num.=')'; } else { my @lda=($ld=~m/\d/g); @@ -189,12 +241,285 @@ sub mask { return '^'.$alpha.$num."\$"; } +#------------------------------------------------------- + +=item UWCALC(hashname,modules,units,date) + +returns the proportion of the module +weights not previously completed by the student. + +=over 4 + +=item hashname + +name of the hash the module dates have been inserted into + +=item modules + +reference to a cell which contains a comma deliminated list of modules +covered by the assignment. + +=item units + +reference to a cell which contains a comma deliminated list of module +weights with respect to the assignment + +=item date + +reference to a cell which contains the date the assignment was completed. + +=back + +=cut + +#------------------------------------------------------- +sub UWCALC { + my ($hashname,$modules,$units,$date) = @_; + my @Modules = split(/,/,$modules); + my @Units = split(/,/,$units); + my $total_weight; + foreach (@Units) { + $total_weight += $_; + } + my $usum=0; + for (my $i=0; $i<=$#Modules; $i++) { + if (&HASH($hashname,$Modules[$i]) eq $date) { + $usum += $Units[$i]; + } + } + return $usum/$total_weight; +} + +#------------------------------------------------------- + +=item CDLSUM(list) + +returns the sum of the elements in a cell which contains +a Comma Deliminate List of numerical values. +'list' is a reference to a cell which contains a comma deliminated list. + +=cut + +#------------------------------------------------------- +sub CDLSUM { + my ($list)=@_; + my $sum; + foreach (split/,/,$list) { + $sum += $_; + } + return $sum; +} + +#------------------------------------------------------- + +=item CDLITEM(list,index) + +returns the item at 'index' in a Comma Deliminated List. + +=over 4 + +=item list + +reference to a cell which contains a comma deliminated list. + +=item index + +the Perl index of the item requested (first element in list has +an index of 0) + +=back + +=cut + +#------------------------------------------------------- +sub CDLITEM { + my ($list,$index)=@_; + my @Temp = split/,/,$list; + return $Temp[$index]; +} + +#------------------------------------------------------- + +=item CDLHASH(name,key,value) + +loads a comma deliminated list of keys into +the hash 'name', all with a value of 'value'. + +=over 4 + +=item name + +name of the hash. + +=item key + +(a pointer to) a comma deliminated list of keys. + +=item value + +a single value to be entered for each key. + +=back + +=cut + +#------------------------------------------------------- +sub CDLHASH { + my ($name,$key,$value)=@_; + my @Keys; + my @Values; + # Check to see if we have multiple $key values + if ($key =~ /[A-z](\-[A-z])?\d+(\-\d+)?/) { + my $keymask = &mask($key); + # Assume the keys are addresses + my @Temp = grep /$keymask/,keys(%v); + @Keys = $v{@Temp}; + } else { + $Keys[0]= $key; + } + my @Temp; + foreach $key (@Keys) { + @Temp = (@Temp, split/,/,$key); + } + @Keys = @Temp; + if ($value =~ /[A-z](\-[A-z])?\d+(\-\d+)?/) { + my $valmask = &mask($value); + my @Temp = grep /$valmask/,keys(%v); + @Values =$v{@Temp}; + } else { + $Values[0]= $value; + } + $value = $Values[0]; + # Add values to hash + for (my $i = 0; $i<=$#Keys; $i++) { + my $key = $Keys[$i]; + if (! exists ($hashes{$name}->{$key})) { + $hashes{$name}->{$key}->[0]=$value; + } else { + my @Temp = sort(@{$hashes{$name}->{$key}},$value); + $hashes{$name}->{$key} = \@Temp; + } + } + return "hash '$name' updated"; +} + +#------------------------------------------------------- + +=item GETHASH(name,key,index) + +returns the element in hash 'name' +reference by the key 'key', at index 'index' in the values list. + +=cut + +#------------------------------------------------------- +sub GETHASH { + my ($name,$key,$index)=@_; + if (! defined($index)) { + $index = 0; + } + if ($key =~ /^[A-z]\d+$/) { + $key = $v{$key}; + } + return $hashes{$name}->{$key}->[$index]; +} + +#------------------------------------------------------- + +=item CLEARHASH(name) + +clears all the values from the hash 'name' + +=item CLEARHASH(name,key) + +clears all the values from the hash 'name' associated with the given key. + +=cut + +#------------------------------------------------------- +sub CLEARHASH { + my ($name,$key)=@_; + if (defined($key)) { + if (exists($hashes{$name}->{$key})) { + $hashes{$name}->{$key}=undef; + return "hash '$name' key '$key' cleared"; + } + } else { + if (exists($hashes{$name})) { + $hashes{$name}=undef; + return "hash '$name' cleared"; + } + } + return "Error in clearing hash"; +} + +#------------------------------------------------------- + +=item HASH(name,key,value) + +loads values into an internal hash. If a key +already has a value associated with it, the values are sorted numerically. + +=item HASH(name,key) + +returns the 0th value in the hash 'name' associated with 'key'. + +=cut + +#------------------------------------------------------- +sub HASH { + my ($name,$key,$value)=@_; + my @Keys; + undef @Keys; + my @Values; + # Check to see if we have multiple $key values + if ($key =~ /[A-z](\-[A-z])?\d+(\-\d+)?/) { + my $keymask = &mask($key); + # Assume the keys are addresses + my @Temp = grep /$keymask/,keys(%v); + @Keys = $v{@Temp}; + } else { + $Keys[0]= $key; + } + # If $value is empty, return the first value associated + # with the first key. + if (! $value) { + return $hashes{$name}->{$Keys[0]}->[0]; + } + # Check to see if we have multiple $value(s) + if ($value =~ /[A-z](\-[A-z])?\d+(\-\d+)?/) { + my $valmask = &mask($value); + my @Temp = grep /$valmask/,keys(%v); + @Values =$v{@Temp}; + } else { + $Values[0]= $value; + } + # Add values to hash + for (my $i = 0; $i<=$#Keys; $i++) { + my $key = $Keys[$i]; + my $value = ($i<=$#Values ? $Values[$i] : $Values[0]); + if (! exists ($hashes{$name}->{$key})) { + $hashes{$name}->{$key}->[0]=$value; + } else { + my @Temp = sort(@{$hashes{$name}->{$key}},$value); + $hashes{$name}->{$key} = \@Temp; + } + } + return $Values[-1]; +} + +#------------------------------------------------------- + +=item NUM(range) + +returns the number of items in the range. + +=cut + +#------------------------------------------------------- sub NUM { my $mask=mask(@_); - my $num=0; - map { - $num++; - } grep /$mask/,keys %v; + my $num= $#{@{grep(/$mask/,keys(%v))}}+1; return $num; } @@ -202,31 +527,49 @@ sub BIN { my ($low,$high,$lower,$upper)=@_; my $mask=mask($lower,$upper); my $num=0; - map { + foreach (grep /$mask/,keys(%v)) { if (($v{$_}>=$low) && ($v{$_}<=$high)) { $num++; } - } grep /$mask/,keys %v; + } return $num; } +#------------------------------------------------------- + +=item SUM(range) + +returns the sum of items in the range. + +=cut + +#------------------------------------------------------- sub SUM { my $mask=mask(@_); my $sum=0; - map { + foreach (grep /$mask/,keys(%v)) { $sum+=$v{$_}; - } grep /$mask/,keys %v; + } return $sum; } +#------------------------------------------------------- + +=item MEAN(range) + +compute the average of the items in the range. + +=cut + +#------------------------------------------------------- sub MEAN { my $mask=mask(@_); my $sum=0; my $num=0; - map { + foreach (grep /$mask/,keys(%v)) { $sum+=$v{$_}; $num++; - } grep /$mask/,keys %v; + } if ($num) { return $sum/$num; } else { @@ -234,58 +577,104 @@ sub MEAN { } } +#------------------------------------------------------- + +=item STDDEV(range) + +compute the standard deviation of the items in the range. + +=cut + +#------------------------------------------------------- sub STDDEV { my $mask=mask(@_); my $sum=0; my $num=0; - map { + foreach (grep /$mask/,keys(%v)) { $sum+=$v{$_}; $num++; - } grep /$mask/,keys %v; + } unless ($num>1) { return undef; } my $mean=$sum/$num; $sum=0; - map { + foreach (grep /$mask/,keys(%v)) { $sum+=($v{$_}-$mean)**2; - } grep /$mask/,keys %v; + } return sqrt($sum/($num-1)); } +#------------------------------------------------------- + +=item PROD(range) + +compute the product of the items in the range. + +=cut + +#------------------------------------------------------- sub PROD { my $mask=mask(@_); my $prod=1; - map { + foreach (grep /$mask/,keys(%v)) { $prod*=$v{$_}; - } grep /$mask/,keys %v; + } return $prod; } +#------------------------------------------------------- + +=item MAX(range) + +compute the maximum of the items in the range. + +=cut + +#------------------------------------------------------- sub MAX { my $mask=mask(@_); my $max='-'; - map { + foreach (grep /$mask/,keys(%v)) { unless ($max) { $max=$v{$_}; } if (($v{$_}>$max) || ($max eq '-')) { $max=$v{$_}; } - } grep /$mask/,keys %v; + } return $max; } +#------------------------------------------------------- + +=item MIN(range) + +compute the minimum of the items in the range. + +=cut + +#------------------------------------------------------- sub MIN { my $mask=mask(@_); my $min='-'; - map { + foreach (grep /$mask/,keys(%v)) { unless ($max) { $max=$v{$_}; } if (($v{$_}<$min) || ($min eq '-')) { $min=$v{$_}; } - } grep /$mask/,keys %v; + } return $min; } +#------------------------------------------------------- + +=item SUMMAX(num,lower,upper) + +compute the sum of the largest 'num' items in the range from +'lower' to 'upper' + +=cut + +#------------------------------------------------------- sub SUMMAX { my ($num,$lower,$upper)=@_; my $mask=mask($lower,$upper); my @inside=(); - map { - $inside[$#inside+1]=$v{$_}; - } grep /$mask/,keys %v; + foreach (grep /$mask/,keys(%v)) { + push (@inside,$v{$_}); + } @inside=sort(@inside); my $sum=0; my $i; for ($i=$#inside;(($i>$#inside-$num) && ($i>=0));$i--) { @@ -294,13 +683,23 @@ sub SUMMAX { return $sum; } +#------------------------------------------------------- + +=item SUMMIN(num,lower,upper) + +compute the sum of the smallest 'num' items in the range from +'lower' to 'upper' + +=cut + +#------------------------------------------------------- sub SUMMIN { my ($num,$lower,$upper)=@_; my $mask=mask($lower,$upper); my @inside=(); - map { + foreach (grep /$mask/,keys(%v)) { $inside[$#inside+1]=$v{$_}; - } grep /$mask/,keys %v; + } @inside=sort(@inside); my $sum=0; my $i; for ($i=0;(($i<$num) && ($i<=$#inside));$i++) { @@ -316,32 +715,50 @@ sub expandnamed { my @vars=split(/\W+/,$formula); my %values=(); undef %values; - map { + foreach ( @vars ) { my $varname=$_; if ($varname=~/\D/) { $formula=~s/$varname/'$c{\''.$varname.'\'}'/ge; $varname=~s/$var/\(\\w\+\)/g; - map { + foreach (keys(%c)) { if ($_=~/$varname/) { $values{$1}=1; } - } keys %c; + } } - } @vars; + } if ($func eq 'EXPANDSUM') { my $result=''; - map { + foreach (keys(%values)) { my $thissum=$formula; $thissum=~s/$var/$_/g; $result.=$thissum.'+'; - } keys %values; + } $result=~s/\+$//; return $result; } else { return 0; } } else { - return '$c{\''.$expression.'\'}'; + # it is not a function, so it is a parameter name + # We should do the following: + # 1. Take the list of parameter names + # 2. look through the list for ones that match the parameter we want + # 3. If there are no collisions, return the one that matches + # 4. If there is a collision, return 'bad parameter name error' + my $returnvalue = ''; + my @matches = (); + $#matches = -1; + study $expression; + foreach $parameter (keys(%c)) { + push @matches,$parameter if ($parameter =~ /$expression/); + } + if ($#matches == 0) { + $returnvalue = '$c{\''.$matches[0].'\'}'; + } else { + $returnvalue = "'bad parameter name : $expression'"; + } + return $returnvalue; } } @@ -353,27 +770,33 @@ sub sett { } else { $pattern='[A-Z]'; } - map { + foreach (keys(%f)) { if ($_=~/template\_(\w)/) { my $col=$1; unless ($col=~/^$pattern/) { - map { + foreach (keys(%f)) { if ($_=~/A(\d+)/) { my $trow=$1; if ($trow) { + # Get the name of this cell my $lb=$col.$trow; + # Grab the template declaration $t{$lb}=$f{'template_'.$col}; + # Replace '#' with the row number $t{$lb}=~s/\#/$trow/g; + # Replace '....' with ',' $t{$lb}=~s/\.\.+/\,/g; + # Replace 'A0' with the value from 'A0' $t{$lb}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$v\{\'$2\'\}/g; + # Replace parameters $t{$lb}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge; } } - } keys %f; + } } } - } keys %f; - map { + } + foreach (keys(%f)) { if (($f{$_}) && ($_!~/template\_/)) { my $matches=($_=~/^$pattern(\d+)/); if (($matches) && ($1)) { @@ -387,7 +810,9 @@ sub sett { $t{$_}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge; } } - } keys %f; + } + # For some reason 'A0' gets special treatment... This seems superfluous + # but I imagine it is here for a reason. $t{'A0'}=$f{'A0'}; $t{'A0'}=~s/\.\.+/\,/g; $t{'A0'}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$v\{\'$2\'\}/g; @@ -401,7 +826,7 @@ sub calc { my $depth=0; while ($notfinished) { $notfinished=0; - map { + foreach (keys(%t)) { my $old=$v{$_}; $v{$_}=eval($t{$_}); if ($@) { @@ -409,7 +834,7 @@ sub calc { return $@; } if ($v{$_} ne $old) { $notfinished=1; } - } keys %t; + } $depth++; if ($depth>100) { %v=(); @@ -422,14 +847,14 @@ sub calc { sub templaterow { my @cols=(); $cols[0]='Template'; - map { + foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M', + 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', + 'a','b','c','d','e','f','g','h','i','j','k','l','m', + 'n','o','p','q','r','s','t','u','v','w','x','y','z') { my $fm=$f{'template_'.$_}; $fm=~s/[\'\"]/\&\#34;/g; $cols[$#cols+1]="'template_$_','$fm'".'___eq___'.$fm; - } ('A','B','C','D','E','F','G','H','I','J','K','L','M', - 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', - 'a','b','c','d','e','f','g','h','i','j','k','l','m', - 'n','o','p','q','r','s','t','u','v','w','x','y','z'); + } return @cols; } @@ -438,28 +863,30 @@ sub outrowassess { my @cols=(); if ($n) { my ($usy,$ufn)=split(/\_\_\&\&\&\_\_/,$f{'A'.$n}); + if ($rl{$usy}) { $cols[0]=$rl{$usy}.'
'. ''; } else { $cols[0]='Export'; } - map { + foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M', + 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', + 'a','b','c','d','e','f','g','h','i','j','k','l','m', + 'n','o','p','q','r','s','t','u','v','w','x','y','z') { my $fm=$f{$_.$n}; $fm=~s/[\'\"]/\&\#34;/g; - $cols[$#cols+1]="'$_$n','$fm'".'___eq___'.$v{$_.$n}; - } ('A','B','C','D','E','F','G','H','I','J','K','L','M', - 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', - 'a','b','c','d','e','f','g','h','i','j','k','l','m', - 'n','o','p','q','r','s','t','u','v','w','x','y','z'); + push(@cols,"'$_$n','$fm'".'___eq___'.$v{$_.$n}); + } return @cols; } @@ -471,23 +898,23 @@ sub outrow { } else { $cols[0]='Export'; } - map { + foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M', + 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', + 'a','b','c','d','e','f','g','h','i','j','k','l','m', + 'n','o','p','q','r','s','t','u','v','w','x','y','z') { my $fm=$f{$_.$n}; $fm=~s/[\'\"]/\&\#34;/g; $cols[$#cols+1]="'$_$n','$fm'".'___eq___'.$v{$_.$n}; - } ('A','B','C','D','E','F','G','H','I','J','K','L','M', - 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', - 'a','b','c','d','e','f','g','h','i','j','k','l','m', - 'n','o','p','q','r','s','t','u','v','w','x','y','z'); + } return @cols; } sub exportrowa { my @exportarray=(); - map { + foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M', + 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') { $exportarray[$#exportarray+1]=$v{$_.'0'}; - } ('A','B','C','D','E','F','G','H','I','J','K','L','M', - 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z'); + } return @exportarray; } @@ -698,7 +1125,7 @@ sub rown { $maxred=26; } if ($n eq '-') { $proc='&templaterow'; $n=-1; $dataflag=1; } - map { + foreach ($safeeval->reval($proc.'('.$n.')')) { my $bgcolor=$defaultbg.((($showf-1)/5==int(($showf-1)/5))?'99':'DD'); my ($fm,$vl)=split(/\_\_\_eq\_\_\_/,$_); if ((($vl ne '') || ($vl eq '0')) && @@ -721,7 +1148,7 @@ sub rown { $rowdata.=',"'.$vl.'"'; } $showf++; - } $safeeval->reval($proc.'('.$n.')'); + } # End of foreach($safeval...) if ($ENV{'form.showall'} || ($dataflag)) { return $rowdata.($ENV{'form.showcsv'}?'':''); } else { @@ -756,7 +1183,10 @@ sub outsheet { 'Calculations'; my $showf=0; - map { + foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M', + 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', + 'a','b','c','d','e','f','g','h','i','j','k','l','m', + 'n','o','p','q','r','s','t','u','v','w','x','y','z') { $showf++; if ($showf<=$maxred) { $tabledata.=''; @@ -764,10 +1194,7 @@ sub outsheet { $tabledata.=''; } $tabledata.="$_"; - } ('A','B','C','D','E','F','G','H','I','J','K','L','M', - 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', - 'a','b','c','d','e','f','g','h','i','j','k','l','m', - 'n','o','p','q','r','s','t','u','v','w','x','y','z'); + } $tabledata.=''.&rown($safeeval,'-').&rown($safeeval,0); } else { $tabledata='
'; }
 
@@ -799,12 +1226,12 @@ sub outsheet {
 	$r->print("\n
\n"); $r->rflush(); $r->print(''); - map { + foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M', + 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', + 'a','b','c','d','e','f','g','h','i','j','k','l','m', + 'n','o','p','q','r','s','t','u','v','w','x','y','z') { $r->print(''); - } ('A','B','C','D','E','F','G','H','I','J','K','L','M', - 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', - 'a','b','c','d','e','f','g','h','i','j','k','l','m', - 'n','o','p','q','r','s','t','u','v','w','x','y','z'); + } $r->print(''); } $n++; @@ -817,26 +1244,51 @@ sub outsheet { # # ----------------------------------------------- Read list of available sheets # - sub othersheets { my ($safeeval,$stype)=@_; - + # my $cnum=&getcnum($safeeval); my $cdom=&getcdom($safeeval); my $chome=&getchome($safeeval); - + # my @alternatives=(); - my $result=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum.':'. - $stype.'_spreadsheets',$chome); - if ($result!~/^error\:/) { - map { - $alternatives[$#alternatives+1]= - &Apache::lonnet::unescape((split(/\=/,$_))[0]); - } split(/\&/,$result); - } + my %results=&Apache::lonnet::dump($stype.'_spreadsheets',$cdom,$cnum); + my ($tmp) = keys(%results); + unless ($tmp =~ /^(con_lost|error|no_such_host)/i) { + @alternatives = sort (keys(%results)); + } return @alternatives; } + +# +# -------------------------------------- Parse a spreadsheet +# +sub parse_sheet { + # $sheetxml is a scalar reference or a scalar + my ($sheetxml) = @_; + if (! ref($sheetxml)) { + my $tmp = $sheetxml; + $sheetxml = \$tmp; + } + my %f; + my $parser=HTML::TokeParser->new($sheetxml); + my $token; + while ($token=$parser->get_token) { + if ($token->[0] eq 'S') { + if ($token->[1] eq 'field') { + $f{$token->[2]->{'col'}.$token->[2]->{'row'}}= + $parser->get_text('/field'); + } + if ($token->[1] eq 'template') { + $f{'template_'.$token->[2]->{'col'}}= + $parser->get_text('/template'); + } + } + } + return \%f; +} + # # -------------------------------------- Read spreadsheet formulas for a course # @@ -848,17 +1300,22 @@ sub readsheet { my $cdom=&getcdom($safeeval); my $chome=&getchome($safeeval); -# --------- There is no filename. Look for defaults in course and global, cache - - unless($fn) { + if (! defined($fn)) { + # There is no filename. Look for defaults in course and global, cache unless ($fn=$defaultsheets{$cnum.'_'.$cdom.'_'.$stype}) { - $fn=&Apache::lonnet::reply('get:'.$cdom.':'.$cnum. - ':environment:spreadsheet_default_'.$stype, - $chome); - unless (($fn) && ($fn!~/^error\:/)) { - $fn='default_'.$stype; - } - $defaultsheets{$cnum.'_'.$cdom.'_'.$stype}=$fn; + my %tmphash = &Apache::lonnet::get('environment', + ['spreadsheet_default_'.$stype], + $cdom,$cnum); + my ($tmp) = keys(%tmphash); + if ($tmp =~ /^(con_lost|error|no_such_host)/i) { + $fn = 'default_'.$stype; + } else { + $fn = $tmphash{'spreadsheet_default_'.$stype}; + } + unless (($fn) && ($fn!~/^error\:/)) { + $fn='default_'.$stype; + } + $defaultsheets{$cnum.'_'.$cdom.'_'.$stype}=$fn; } } @@ -877,44 +1334,34 @@ sub readsheet { my %f=(); if ($fn=~/^default\_/) { - my $sheetxml=''; - { + my $sheetxml=''; my $fh; my $dfn=$fn; $dfn=~s/\_/\./g; if ($fh=Apache::File->new($includedir.'/'.$dfn)) { - $sheetxml=join('',<$fh>); - } else { + $sheetxml=join('',<$fh>); + } else { $sheetxml='"Error"'; - } - } - my $parser=HTML::TokeParser->new(\$sheetxml); - my $token; - while ($token=$parser->get_token) { - if ($token->[0] eq 'S') { - if ($token->[1] eq 'field') { - $f{$token->[2]->{'col'}.$token->[2]->{'row'}}= - $parser->get_text('/field'); - } - if ($token->[1] eq 'template') { - $f{'template_'.$token->[2]->{'col'}}= - $parser->get_text('/template'); + } + %f=%{&parse_sheet(\$sheetxml)}; + } elsif($fn=~/\/*\.spreadsheet$/) { + my $sheetxml=&Apache::lonnet::getfile + (&Apache::lonnet::filelocation('',$fn)); + if ($sheetxml == -1) { + $sheetxml='"Error loading spreadsheet ' + .$fn.'"'; + } + %f=%{&parse_sheet(\$sheetxml)}; + } else { + my $sheet=''; + my %tmphash = &Apache::lonnet::dump($fn,$cdom,$cnum); + my ($tmp) = keys(%tmphash); + unless ($tmp =~ /^(con_lost|error|no_such_host)/i) { + foreach (keys(%tmphash)) { + $f{$_}=$tmphash{$_}; } - } - } - } else { - my $sheet=''; - my $reply=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum.':'.$fn, - $chome); - unless ($reply=~/^error\:/) { - $sheet=$reply; - } - map { - my ($name,$value)=split(/\=/,$_); - $f{&Apache::lonnet::unescape($name)}= - &Apache::lonnet::unescape($value); - } split(/\&/,$sheet); - } + } + } # --------------------------------------------------------------- Cache and set $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f); &setformulas($safeeval,%f); @@ -959,12 +1406,12 @@ sub writesheet { $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f); # ----------------------------------------------------------------- Write sheet my $sheetdata=''; - map { + foreach (keys(%f)) { unless ($f{$_} eq 'import') { $sheetdata.=&Apache::lonnet::escape($_).'='. &Apache::lonnet::escape($f{$_}).'&'; } - } keys %f; + } $sheetdata=~s/\&$//; my $reply=&Apache::lonnet::reply('put:'.$cdom.':'.$cnum.':'.$fn.':'. $sheetdata,$chome); @@ -1021,6 +1468,7 @@ sub tmpread { $fn=$tmpdir.$fn.'.tmp'; my $fh; my %fo=(); + my $countrows=0; if ($fh=Apache::File->new($fn)) { my $name; while ($name=<$fh>) { @@ -1028,6 +1476,11 @@ sub tmpread { my $value=<$fh>; chomp($value); $fo{$name}=$value; + if ($name=~/^A(\d+)$/) { + if ($1>$countrows) { + $countrows=$1; + } + } } } if ($nform eq 'changesheet') { @@ -1035,6 +1488,13 @@ sub tmpread { unless ($ENV{'form.sel_'.$nfield} eq 'Default') { $fo{'A'.$nfield}.='__&&&__'.$ENV{'form.sel_'.$nfield}; } + } elsif ($nfield eq 'insertrow') { + $countrows++; + if ($nform eq 'top') { + $fo{'A'.$countrows}='AAAAA_'.$countrows; + } else { + $fo{'A'.$countrows}='zzzzz_'.$countrows; + } } else { if ($nfield) { $fo{$nfield}=$nform; } } @@ -1145,7 +1605,7 @@ sub updateclasssheet { my %currentlist=(); my $now=time; unless ($classlst=~/^error\:/) { - map { + foreach (split(/\&/,$classlst)) { my ($name,$value)=split(/\=/,$_); my ($end,$start)=split(/\:/,&Apache::lonnet::unescape($value)); my $active=1; @@ -1172,24 +1632,24 @@ sub updateclasssheet { $rowlabel=''. $ssec.' '.$reply{$sname}.'
'; - map { + foreach ( split(/\&/,$reply)) { $rowlabel.=&Apache::lonnet::unescape($_).' '; - } split(/\&/,$reply); + } $rowlabel.='
'; } else { $rowlabel=$ssec.'","'.$reply{$sname}.'"'; my $ncount=0; - map { + foreach (split(/\&/,$reply)) { $rowlabel.=',"'.&Apache::lonnet::unescape($_).'"'; $ncount++; - } split(/\&/,$reply); + } unless ($ncount==4) { $rowlabel.=',""'; } $rowlabel=~s/\"$//; } } $currentlist{&Apache::lonnet::unescape($name)}=$rowlabel; } - } split(/\&/,$classlst); + } # end of foreach (split(/\&/,$classlst)) # # -------------------- Find discrepancies between the course row table and this # @@ -1200,7 +1660,7 @@ sub updateclasssheet { my %existing=(); # ----------------------------------------------------------- Now obsolete rows - map { + foreach (keys(%f)) { if ($_=~/^A(\d+)/) { $maxrow=($1>$maxrow)?$1:$maxrow; $existing{$f{$_}}=1; @@ -1209,17 +1669,17 @@ sub updateclasssheet { $changed=1; } } - } keys %f; + } # -------------------------------------------------------- New and unknown keys - map { + foreach (sort keys(%currentlist)) { unless ($existing{$_}) { $changed=1; $maxrow++; $f{'A'.$maxrow}=$_; } - } sort keys %currentlist; + } if ($changed) { &setformulas($safeeval,%f); } @@ -1245,7 +1705,13 @@ sub updatestudentassesssheet { # --------------------------------------------------------- Get all assessments my %allkeys=('timestamp' => - 'Timestamp of Last Transaction
timestamp'); + 'Timestamp of Last Transaction
timestamp', + 'subnumber' => + 'Number of Submissions
subnumber', + 'tutornumber' => + 'Number of Tutor Responses
tutornumber', + 'totalpoints' => + 'Total Points Granted
totalpoints'); my $adduserstr=''; if ((&getuname($safeeval) ne $ENV{'user.name'}) || @@ -1262,10 +1728,13 @@ sub updatestudentassesssheet { '">Evaluation', '_tutoring' => 'Tutoring' + '">Tutoring', + '_discussion' => + 'Discussion' ); - map { + foreach (keys(%bighash)) { if ($_=~/^src\_(\d+)\.(\d+)$/) { my $mapid=$1; my $resid=$2; @@ -1280,7 +1749,8 @@ sub updatestudentassesssheet { ''. $bighash{'title_'.$id}.''; if ($stype eq 'assesscalc') { - map { + foreach (split(/\,/, + &Apache::lonnet::metadata($srcf,'keys'))) { if (($_=~/^stores\_(.*)/) || ($_=~/^parameter\_(.*)/)) { my $key=$_; my $display= @@ -1292,11 +1762,11 @@ sub updatestudentassesssheet { $display.='
'.$key; $allkeys{$key}=$display; } - } split(/\,/,&Apache::lonnet::metadata($srcf,'keys')); + } # end of foreach } } } - } keys %bighash; + } # end of foreach (keys(%bighash)) untie(%bighash); # @@ -1328,7 +1798,7 @@ sub updatestudentassesssheet { my %existing=(); # ----------------------------------------------------------- Now obsolete rows - map { + foreach (keys(%f)) { if ($_=~/^A(\d+)/) { $maxrow=($1>$maxrow)?$1:$maxrow; my ($usy,$ufn)=split(/\_\_\&\&\&\_\_/,$f{$_}); @@ -1341,17 +1811,17 @@ sub updatestudentassesssheet { =~s/assesscalc\?usymb\=/assesscalc\?ufn\=$ufn\&usymb\=/; } } - } keys %f; + } # -------------------------------------------------------- New and unknown keys - map { + foreach (keys(%current)) { unless ($existing{$_}) { $changed=1; $maxrow++; $f{'A'.$maxrow}=$_; } - } keys %current; + } if ($changed) { &setformulas($safeeval,%f); } @@ -1376,15 +1846,15 @@ sub loadstudent { &getcid($safeeval), &getuhome($safeeval)); unless ($reply=~/^error\:/) { - map { + foreach ( split(/\&/,$reply)) { my ($name,$value)=split(/\=/,$_); $cachedstores{&Apache::lonnet::unescape($name)}= &Apache::lonnet::unescape($value); - } split(/\&/,$reply); + } } } my @assessdata=(); - map { + foreach (keys(%f)) { if ($_=~/^A(\d+)/) { my $row=$1; unless (($f{$_}=~/^\!/) || ($row==0)) { @@ -1393,7 +1863,8 @@ sub loadstudent { &getudom($safeeval), 'assesscalc',$usy,$ufn); my $index=0; - map { + foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M', + 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') { if ($assessdata[$index]) { my $col=$_; if ($assessdata[$index]=~/\D/) { @@ -1406,11 +1877,10 @@ sub loadstudent { } } $index++; - } ('A','B','C','D','E','F','G','H','I','J','K','L','M', - 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z'); + } } } - } keys %f; + } $cachedassess=''; undef %cachedstores; &setformulas($safeeval,%f); @@ -1424,11 +1894,11 @@ sub loadcourse { my %c=(); my %f=&getformulas($safeeval); my $total=0; - map { + foreach (keys(%f)) { if ($_=~/^A(\d+)/) { unless ($f{$_}=~/^\!/) { $total++; } } - } keys %f; + } my $now=0; my $since=time; $r->print(< ENDPOP $r->rflush(); - map { + foreach (keys(%f)) { if ($_=~/^A(\d+)/) { my $row=$1; unless (($f{$_}=~/^\!/) || ($row==0)) { @@ -1457,7 +1927,8 @@ ENDPOP $r->rflush(); my $index=0; - map { + foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M', + 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') { if ($studentdata[$index]) { my $col=$_; if ($studentdata[$index]=~/\D/) { @@ -1470,11 +1941,10 @@ ENDPOP } } $index++; - } ('A','B','C','D','E','F','G','H','I','J','K','L','M', - 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z'); + } } } - } keys %f; + } &setformulas($safeeval,%f); &setconstants($safeeval,%c); $r->print(''); @@ -1510,9 +1980,9 @@ sub loadassessment { my $version=$cachedstores{'version:'.$symb}; my $scope; for ($scope=1;$scope<=$version;$scope++) { - map { + foreach (split(/\:/,$cachedstores{$scope.':keys:'.$symb})) { $returnhash{$_}=$cachedstores{$scope.':'.$symb.':'.$_}; - } split(/\:/,$cachedstores{$scope.':keys:'.$symb}); + } } } else { @@ -1524,20 +1994,32 @@ sub loadassessment { "restore:$udom:$uname:". &Apache::lonnet::escape($namespace).":". &Apache::lonnet::escape($symb),$uhome); - map { + foreach (split(/\&/,$answer)) { my ($name,$value)=split(/\=/,$_); $returnhash{&Apache::lonnet::unescape($name)}= &Apache::lonnet::unescape($value); - } split(/\&/,$answer); + } my $version; for ($version=1;$version<=$returnhash{'version'};$version++) { - map { + foreach (split(/\:/,$returnhash{$version.':keys'})) { $returnhash{$_}=$returnhash{$version.':'.$_}; - } split(/\:/,$returnhash{$version.':keys'}); + } } } # ----------------------------- returnhash now has all stores for this resource +# --------- convert all "_" to "." to be able to use libraries, multiparts, etc + + my @oldkeys=keys %returnhash; + + foreach (@oldkeys) { + my $name=$_; + my $value=$returnhash{$_}; + delete $returnhash{$_}; + $name=~s/\_/\./g; + $returnhash{$name}=$value; + } + # ---------------------------- initialize coursedata and userdata for this user undef %courseopt; undef %useropt; @@ -1555,11 +2037,11 @@ sub loadassessment { $courserdatas{$cid.'.last_cache'}=time; } } - map { + foreach (split(/\&/,$courserdatas{$cid})) { my ($name,$value)=split(/\=/,$_); $courseopt{$userprefix.&Apache::lonnet::unescape($name)}= &Apache::lonnet::unescape($value); - } split(/\&/,$courserdatas{$cid}); + } # --------------------------------------------------- Get userdata (if present) unless ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<240) { @@ -1570,11 +2052,11 @@ sub loadassessment { $userrdatas{$uname.'___'.$udom.'.last_cache'}=time; } } - map { + foreach (split(/\&/,$userrdatas{$uname.'___'.$udom})) { my ($name,$value)=split(/\=/,$_); $useropt{$userprefix.&Apache::lonnet::unescape($name)}= &Apache::lonnet::unescape($value); - } split(/\&/,$userrdatas{$uname.'___'.$udom}); + } } # ----------------- now courseopt, useropt initialized for this user and course # (used by parmval) @@ -1585,9 +2067,9 @@ sub loadassessment { my %thisassess=(); my ($symap,$syid,$srcf)=split(/\_\_\_/,$symb); - map { + foreach (split(/\,/,&Apache::lonnet::metadata($srcf,'keys'))) { $thisassess{$_}=1; - } split(/\,/,&Apache::lonnet::metadata($srcf,'keys')); + } # # Load parameters # @@ -1596,7 +2078,7 @@ sub loadassessment { if (tie(%parmhash,'GDBM_File', &getcfn($safeeval).'_parms.db',&GDBM_READER,0640)) { my %f=&getformulas($safeeval); - map { + foreach (keys(%f)) { if ($_=~/^A/) { unless ($f{$_}=~/^\!/) { if ($f{$_}=~/^parameter/) { @@ -1615,7 +2097,7 @@ sub loadassessment { } } } - } keys %f; + } untie(%parmhash); } &setconstants($safeeval,%c); @@ -1637,11 +2119,11 @@ sub hiddenfield { sub selectbox { my ($title,$name,$value,%options)=@_; my $selout="\n

$title:
".''; } @@ -1716,6 +2198,11 @@ sub forcedrecalc { sub exportsheet { my ($uname,$udom,$stype,$usymb,$fn)=@_; my @exportarr=(); + + if (($usymb=~/^\_(\w+)/) && (!$fn)) { + $fn='default_'.$1; + } + # # Check if cached # @@ -1724,23 +2211,23 @@ sub exportsheet { my $found=''; if ($oldsheets{$key}) { - map { + foreach (split(/\_\_\_\&\_\_\_/,$oldsheets{$key})) { my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_); if ($name eq $fn) { $found=$value; } - } split(/\_\_\_\&\_\_\_/,$oldsheets{$key}); + } } unless ($found) { &cachedssheets($uname,$udom,&Apache::lonnet::homeserver($uname,$udom)); if ($oldsheets{$key}) { - map { + foreach (split(/\_\_\_\&\_\_\_/,$oldsheets{$key})) { my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_); if ($name eq $fn) { $found=$value; } - } split(/\_\_\_\&\_\_\_/,$oldsheets{$key}); + } } } # @@ -1793,18 +2280,18 @@ sub exportsheet { } my %currentlystored=(); unless ($current=~/^error\:/) { - map { + foreach (split(/\_\_\_\&\_\_\_/,&Apache::lonnet::unescape($current))) { my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_); $currentlystored{$name}=$value; - } split(/\_\_\_\&\_\_\_/,&Apache::lonnet::unescape($current)); + } } $currentlystored{$fn}=join('___;___',@exportarr); my $newstore=''; - map { + foreach (keys(%currentlystored)) { if ($newstore) { $newstore.='___&___'; } $newstore.=$_.'___=___'.$currentlystored{$_}; - } keys %currentlystored; + } my $now=time; if ($stype eq 'studentcalc') { &Apache::lonnet::reply('put:'. @@ -1843,11 +2330,11 @@ sub expirationdates { ':nohist_expirationdates', $ENV{'course.'.$cid.'.home'}); unless ($reply=~/^error\:/) { - map { + foreach (split(/\&/,$reply)) { my ($name,$value)=split(/\=/,$_); $expiredates{&Apache::lonnet::unescape($name)} =&Apache::lonnet::unescape($value); - } split(/\&/,$reply); + } } } @@ -1864,11 +2351,11 @@ sub cachedcsheets { ':nohist_calculatedsheets', $ENV{'course.'.$cid.'.home'}); unless ($reply=~/^error\:/) { - map { + foreach ( split(/\&/,$reply)) { my ($name,$value)=split(/\=/,$_); $oldsheets{&Apache::lonnet::unescape($name)} =&Apache::lonnet::unescape($value); - } split(/\&/,$reply); + } } } @@ -1886,11 +2373,11 @@ sub cachedssheets { $ENV{'request.course.id'}, $shome); unless ($reply=~/^error\:/) { - map { + foreach ( split(/\&/,$reply)) { my ($name,$value)=split(/\=/,$_); $oldsheets{&Apache::lonnet::unescape($name)} =&Apache::lonnet::unescape($value); - } split(/\&/,$reply); + } } $loadedcaches{$sname.'_'.$sdom}=1; } @@ -1928,17 +2415,8 @@ $tmpdir=$r->dir_config('lonDaemons').'/t # --------------------------- Get query string for limited number of parameters - map { - my ($name, $value) = split(/=/,$_); - $value =~ tr/+/ /; - $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg; - if (($name eq 'uname') || ($name eq 'udom') || - ($name eq 'usymb') || ($name eq 'ufn')) { - unless ($ENV{'form.'.$name}) { - $ENV{'form.'.$name}=$value; - } - } - } (split(/&/,$ENV{'QUERY_STRING'})); + &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, + ['uname','udom','usymb','ufn']); if (($ENV{'form.usymb'}=~/^\_(\w+)/) && (!$ENV{'form.ufn'})) { $ENV{'form.ufn'}='default_'.$1; @@ -1976,8 +2454,8 @@ $tmpdir=$r->dir_config('lonDaemons').'/t function celledit(cn,cf) { var cnf=prompt(cn,cf); - if (cnf!=null) { - document.sheet.unewfield.value=cn; + if (cnf!=null) { + document.sheet.unewfield.value=cn; document.sheet.unewformula.value=cnf; document.sheet.submit(); } @@ -1989,6 +2467,12 @@ $tmpdir=$r->dir_config('lonDaemons').'/t document.sheet.submit(); } + function insertrow(cn) { + document.sheet.unewfield.value='insertrow'; + document.sheet.unewformula.value=cn; + document.sheet.submit(); + } + ENDSCRIPT $r->print(''. @@ -2052,6 +2536,9 @@ ENDSCRIPT } else { $r->print('
Section/Group: '.&getcsec($asheet)); } + if ($ENV{'form.usymb'}) { + $r->print('
Assessment: '.$ENV{'form.usymb'}.''); + } } # ---------------------------------------------------------------- Course title @@ -2111,13 +2598,13 @@ ENDSCRIPT $r->print('

'. '

'); if (&gettype($asheet) eq 'studentcalc') { &setothersheets($asheet,&othersheets($asheet,'assesscalc')); @@ -2186,15 +2673,40 @@ ENDSCRIPT } else { $r->print('
Show empty rows: '); } - $r->print('print(' checked'); } + + $r->print(&hiddenfield('userselhidden','true'). + 'print(' checked'); + } else { + unless ($ENV{'form.userselhidden'}) { + unless + ($ENV{'course.'.$ENV{'request.course.id'}.'.hideemptyrows'} eq 'yes') { + $r->print(' checked'); + $ENV{'form.showall'}=1; + } + } + } $r->print('>'); + if (&gettype($asheet) eq 'classcalc') { $r->print( ' Output CSV format: print(' checked'); } $r->print('>'); } + +# ------------------------------------------------------------------ Insertrows + + $r->print(< + +
+ENDINSERTBUTTONS + # ------------------------------------------------------------- Print out sheet &outsheet($r,$asheet); @@ -2213,3 +2725,7 @@ ENDSCRIPT 1; __END__ + + + +

 '.$what.''.$_.'