# The LearningOnline Network with CAPA # Spreadsheet/Grades Display Handler # # 11/11,11/15,11/27,12/04,12/05 Gerd Kortemeyer package Apache::lonspreadsheet; use strict; use Safe; use Safe::Hole; use Opcode; use Apache::lonnet; use Apache::Constants qw(:common); use HTML::TokeParser; # ============================================================================= # ===================================== Implements an instance of a spreadsheet sub initsheet { my $safeeval = new Safe; my $safehole = new Safe::Hole; $safeeval->permit("entereval"); $safeeval->permit(":base_math"); $safeeval->permit("sort"); $safeeval->deny(":base_io"); $safehole->wrap(\&Apache::lonnet::EXT,$safeeval,'&EXT'); my $code=<<'ENDDEFS'; # ---------------------------------------------------- Inside of the safe space # # f: formulas # t: intermediate format (variable references expanded) # v: output values # %v=(); %t=(); %f=(); $sheettype=''; $filename=''; sub mask { my ($lower,$upper)=@_; $lower=~/([A-Z]|\*)(\d+|\*)/; my $la=$1; my $ld=$2; $upper=~/([A-Z]|\*)(\d+|\*)/; my $ua=$1; my $ud=$2; my $alpha=''; my $num=''; if (($la eq '*') || ($ua eq '*')) { $alpha='[A-Z]'; } else { $alpha='['.$la.'-'.$ua.']'; } if (($ld eq '*') || ($ud eq '*')) { $num='\d+'; } else { if (length($ld)!=length($ud)) { $num.='('; map { $num.='['.$_.'-9]'; } ($ld=~m/\d/g); if (length($ud)-length($ld)>1) { $num.='|\d{'.(length($ld)+1).','.(length($ud)-1).'}'; } $num.='|'; map { $num.='[0-'.$_.']'; } ($ud=~m/\d/g); $num.=')'; } else { my @lda=($ld=~m/\d/g); my @uda=($ud=~m/\d/g); my $i; $j=0; for ($i=0;$i<=$#lda;$i++) { if ($lda[$i]==$uda[$i]) { $num.=$lda[$i]; $j=$i; } } if ($j<$#lda-1) { $num.='('.$lda[$j+1]; for ($i=$j+2;$i<=$#lda;$i++) { $num.='['.$lda[$i].'-9]'; } if ($uda[$j+1]-$lda[$j+1]>1) { $num.='|['.($lda[$j+1]+1).'-'.($uda[$j+1]-1).']\d{'. ($#lda-$j-1).'}'; } $num.='|'.$uda[$j+1]; for ($i=$j+2;$i<=$#uda;$i++) { $num.='[0-'.$uda[$i].']'; } $num.=')'; } else { $num.='['.$lda[$#lda].'-'.$uda[$#uda].']'; } } } return '^'.$alpha.$num."\$"; } sub NUM { my $mask=mask(@_); my $num=0; map { $num++; } grep /$mask/,keys %v; return $num; } sub BIN { my ($low,$high,$lower,$upper)=@_; my $mask=mask($lower,$upper); my $num=0; map { if (($v{$_}>=$low) && ($v{$_}<=$high)) { $num++; } } grep /$mask/,keys %v; return $num; } sub SUM { my $mask=mask(@_); my $sum=0; map { $sum+=$v{$_}; } grep /$mask/,keys %v; return $sum; } sub MEAN { my $mask=mask(@_); my $sum=0; my $num=0; map { $sum+=$v{$_}; $num++; } grep /$mask/,keys %v; if ($num) { return $sum/$num; } else { return undef; } } sub STDDEV { my $mask=mask(@_); my $sum=0; my $num=0; map { $sum+=$v{$_}; $num++; } grep /$mask/,keys %v; unless ($num>1) { return undef; } my $mean=$sum/$num; $sum=0; map { $sum+=($v{$_}-$mean)**2; } grep /$mask/,keys %v; return sqrt($sum/($num-1)); } sub PROD { my $mask=mask(@_); my $prod=1; map { $prod*=$v{$_}; } grep /$mask/,keys %v; return $prod; } sub MAX { my $mask=mask(@_); my $max='-'; map { unless ($max) { $max=$v{$_}; } if (($v{$_}>$max) || ($max eq '-')) { $max=$v{$_}; } } grep /$mask/,keys %v; return $max; } sub MIN { my $mask=mask(@_); my $min='-'; map { unless ($max) { $max=$v{$_}; } if (($v{$_}<$min) || ($min eq '-')) { $min=$v{$_}; } } grep /$mask/,keys %v; return $min; } sub SUMMAX { my ($num,$lower,$upper)=@_; my $mask=mask($lower,$upper); my @inside=(); map { $inside[$#inside+1]=$v{$_}; } grep /$mask/,keys %v; @inside=sort(@inside); my $sum=0; my $i; for ($i=$#inside;(($i>$#inside-$num) && ($i>=0));$i--) { $sum+=$inside[$i]; } return $sum; } sub SUMMIN { my ($num,$lower,$upper)=@_; my $mask=mask($lower,$upper); my @inside=(); map { $inside[$#inside+1]=$v{$_}; } grep /$mask/,keys %v; @inside=sort(@inside); my $sum=0; my $i; for ($i=0;(($i<$num) && ($i<=$#inside));$i++) { $sum+=$inside[$i]; } return $sum; } sub sett { %t=(); map { if ($f{$_}) { $t{$_}=$f{$_}; $t{$_}=~s/\.+/\,/g; $t{$_}=~s/(^|[^\"\'])([A-Z]\d+)/$1\$v\{\'$2\'\}/g; } } keys %f; } sub calc { %v=(); &sett(); my $notfinished=1; my $depth=0; while ($notfinished) { $notfinished=0; map { my $old=$v{$_}; $v{$_}=eval($t{$_}); if ($@) { %v=(); return $@; } if ($v{$_} ne $old) { $notfinished=1; } } keys %t; $depth++; if ($depth>100) { %v=(); return 'Maximum calculation depth exceeded'; } } return ''; } # ------------------------------------------- End of "Inside of the safe space" ENDDEFS $safeeval->reval($code); return $safeeval; } # ------------------------------------------------ Add or change formula values sub setformulas { my ($safeeval,@f)=@_; $safeeval->reval('%f=(%f,'."('".join("','",@f)."'));"); } # ------------------------------------------------------- Calculate spreadsheet sub calcsheet { my $safeeval=shift; $safeeval->reval('&calc();'); } # ------------------------------------------------------------------ Get values sub getvalues { my $safeeval=shift; return $safeeval->reval('%v'); } # ---------------------------------------------------------------- Get formulas sub getformulas { my $safeeval=shift; return $safeeval->reval('%f'); } # -------------------------------------------------------------------- Set type sub settype { my ($safeeval,$type)=@_; $safeeval->reval('$sheettype='.$type.';'); } # -------------------------------------------------------------------- Get type sub gettype { my $safeeval=shift; return $safeeval->reval('$sheettype'); } # -------------------------------------------------------------------- Set type sub setfilename { my ($safeeval,$fn)=@_; $safeeval->reval('$filename='.$fn.';'); } # -------------------------------------------------------------------- Get type sub getfilename { my $safeeval=shift; return $safeeval->reval('$filename'); } # ========================================================== End of Spreadsheet # ============================================================================= # --------------------------------------- Read spreadsheet formulas from a file sub readsheet { my ($safeeval,$fn)=shift; &setfilename($safeeval,$fn); $fn=~/\.(\w+)/; &settype($safeeval,$1); my %f=(); my $content; { my $fh=Apache::File->new($fn); $content=join('',<$fh>); } { my $parser=HTML::TokeParser->new(\$content); 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'); } } } } &setformulas($safeeval,%f); } # --------------------------------------------------------------- Read metadata sub readmeta { my $fn=shift; unless ($fn=~/\.meta$/) { $fn.='meta'; } my $content; my %returnhash=(); { my $fh=Apache::File->new($fn); $content=join('',<$fh>); } my $parser=HTML::TokeParser->new(\$content); my $token; while ($token=$parser->get_token) { if ($token->[0] eq 'S') { my $entry=$token->[1]; if (($entry eq 'stores') || ($entry eq 'parameter')) { my $unikey=$entry; $unikey.='_'.$token->[2]->{'part'}; $unikey.='_'.$token->[2]->{'name'}; $returnhash{$unikey}=$token->[2]->{'display'}; } } } return %returnhash; } # ----------------------------------------------------------------- Update rows sub updaterows { my $safeeval=shift; my %bighash; # -------------------------------------------------------------------- Tie hash if (tie(%bighash,'GDBM_File',$ENV{'request.course.fn'}.'.db', &GDBM_READER,0640)) { # --------------------------------------------------------- Get all assessments my %allkeys=(); my %allassess=(); my $stype=&gettype($safeeval); map { if ($_=~/^src\_(\d+)\.(\d+)$/) { my $mapid=$1; my $resid=$2; my $id=$mapid.'.'.$resid; my $srcf=$bighash{$_}; if ($srcf=~/\.(problem|exam|quiz|assess|survey|form)$/) { my $symb= &Apache::lonnet::declutter($bighash{'map_id_'.$mapid}). '___'.$resid.'___'. &Apache::lonnet::declutter($srcf); $allassess{$symb}=$bighash{'title_'.$id}; if ($stype eq 'assesssheet') { map { if ($_=~/^stores\_(.*)/) { my $key=$_; my $display= &Apache::lonnet::metadata($srcf,$key.'.display'); unless ($display) { $display= &Apache::lonnet::metadata($srcf,$key.'.name'); } $allkeys{$key}=$display; } } split(/\,/,&Apache::lonnet::metadata($srcf,'keys')); } } } } keys %bighash; untie(%bighash); # # %allkeys has a list of storage displays by unikey # %allassess has a list of all resource displays by symb # # -------------------- Find discrepancies between the course row table and this # my %f=&getformulas($safeeval); map { if ($_=~/^A/) { if ($stype eq 'assesssheet') { } elsif ($stype eq 'coursesheet') { } } } keys %f; # ------------------------------------------------ Find new and obsolete values } else { return 'Could not access course data'; } } # ----------------------------------------------------------------------------- sub handler { my $r=shift; $r->content_type('text/html'); $r->send_http_header; $r->print('LON-CAPA Spreadsheet'); $r->print(''); my $sheetone=initsheet(); &setformulas($sheetone,('A1' => '5', 'B2' => '6', 'C4' => 'A1+B2')); $r->print(&calcsheet($sheetone)); my %output=&getformulas($sheetone); $r->print('FORM:'.$output{'A1'}.' '.$output{'B2'}.' '.$output{'C4'}); my %output=&getvalues($sheetone); $r->print('
OUT:'.$output{'A1'}.' '.$output{'B2'}.' '.$output{'C4'}); $r->print(''); return OK; } 1; __END__