Annotation of loncom/interface/lonspreadsheet.pm, revision 1.4

1.1       www         1: # The LearningOnline Network with CAPA
                      2: # Spreadsheet/Grades Display Handler
                      3: #
1.3       www         4: # 11/11,11/15,11/27,12/04 Gerd Kortemeyer
1.1       www         5: 
                      6: package Apache::lonspreadsheet;
                      7: 
                      8: use strict;
                      9: use Safe;
1.3       www        10: use Safe::Hole;
1.1       www        11: use Opcode;
                     12: use Apache::lonnet;
                     13: use Apache::Constants qw(:common);
1.3       www        14: use HTML::TokeParser;
                     15: 
1.4     ! www        16: 
        !            17: 
        !            18: sub initsheet {
        !            19:     my $safeeval = new Safe;
        !            20:     my $safehole = new Safe::Hole;
        !            21:     $safeeval->permit("entereval");
        !            22:     $safeeval->permit(":base_math");
        !            23:     $safeeval->permit("sort");
        !            24:     $safeeval->deny(":base_io");
        !            25:     $safehole->wrap(\&Apache::lonnet::EXT,$safeeval,'&EXT');
        !            26:     my $code=<<'ENDDEFS';
        !            27: # ---------------------------------------------------- Inside of the safe space
        !            28: 
1.3       www        29: #
                     30: # f: formulas
1.4     ! www        31: # t: intermediate format (variable references expanded)
        !            32: # v: output values
1.3       www        33: #
                     34: 
1.4     ! www        35: %v=(); 
        !            36: %t=();
        !            37: %f=();
1.1       www        38: 
                     39: sub mask {
                     40:     my ($lower,$upper)=@_;
                     41: 
                     42:     $lower=~/([A-Z]|\*)(\d+|\*)/;
                     43:     my $la=$1;
                     44:     my $ld=$2;
                     45: 
                     46:     $upper=~/([A-Z]|\*)(\d+|\*)/;
                     47:     my $ua=$1;
                     48:     my $ud=$2;
                     49:     my $alpha='';
                     50:     my $num='';
                     51: 
                     52:     if (($la eq '*') || ($ua eq '*')) {
                     53:        $alpha='[A-Z]';
                     54:     } else {
                     55:        $alpha='['.$la.'-'.$ua.']';
                     56:     }   
                     57: 
                     58:     if (($ld eq '*') || ($ud eq '*')) {
                     59: 	$num='\d+';
                     60:     } else {
                     61:         if (length($ld)!=length($ud)) {
                     62:            $num.='(';
                     63: 	   map {
                     64:               $num.='['.$_.'-9]';
                     65:            } ($ld=~m/\d/g);
                     66:            if (length($ud)-length($ld)>1) {
                     67:               $num.='|\d{'.(length($ld)+1).','.(length($ud)-1).'}';
                     68: 	   }
                     69:            $num.='|';
                     70:            map {
                     71:                $num.='[0-'.$_.']';
                     72:            } ($ud=~m/\d/g);
                     73:            $num.=')';
                     74:        } else {
                     75:            my @lda=($ld=~m/\d/g);
                     76:            my @uda=($ud=~m/\d/g);
                     77:            my $i; $j=0;
                     78:            for ($i=0;$i<=$#lda;$i++) {
                     79:                if ($lda[$i]==$uda[$i]) {
                     80: 		   $num.=$lda[$i];
                     81:                    $j=$i;
                     82:                }
                     83:            }
                     84:            if ($j<$#lda-1) {
                     85: 	       $num.='('.$lda[$j+1];
                     86:                for ($i=$j+2;$i<=$#lda;$i++) {
                     87:                    $num.='['.$lda[$i].'-9]';
                     88:                }
                     89:                if ($uda[$j+1]-$lda[$j+1]>1) {
                     90: 		   $num.='|['.($lda[$j+1]+1).'-'.($uda[$j+1]-1).']\d{'.
                     91:                    ($#lda-$j-1).'}';
                     92:                }
                     93: 	       $num.='|'.$uda[$j+1];
                     94:                for ($i=$j+2;$i<=$#uda;$i++) {
                     95:                    $num.='[0-'.$uda[$i].']';
                     96:                }
                     97:                $num.=')';
                     98:            } else {
                     99:                $num.='['.$lda[$#lda].'-'.$uda[$#uda].']';
                    100:            }
                    101:        }
                    102:     }
1.4     ! www       103:     return '^'.$alpha.$num."\$";
1.1       www       104: }
                    105: 
                    106: sub NUM {
                    107:     my $mask=mask(@_);
                    108:     my $num=0;
                    109:     map {
                    110:         $num++;
                    111:     } grep /$mask/,keys %v;
                    112:     return $num;   
                    113: }
                    114: 
                    115: sub BIN {
                    116:     my ($low,$high,$lower,$upper)=@_;
                    117:     my $mask=mask($lower,$upper);
                    118:     my $num=0;
                    119:     map {
                    120:         if (($v{$_}>=$low) && ($v{$_}<=$high)) {
                    121:             $num++;
                    122:         }
                    123:     } grep /$mask/,keys %v;
                    124:     return $num;   
                    125: }
                    126: 
                    127: 
                    128: sub SUM {
                    129:     my $mask=mask(@_);
                    130:     my $sum=0;
                    131:     map {
                    132:         $sum+=$v{$_};
                    133:     } grep /$mask/,keys %v;
                    134:     return $sum;   
                    135: }
                    136: 
                    137: sub MEAN {
                    138:     my $mask=mask(@_);
                    139:     my $sum=0; my $num=0;
                    140:     map {
                    141:         $sum+=$v{$_};
                    142:         $num++;
                    143:     } grep /$mask/,keys %v;
                    144:     if ($num) {
                    145:        return $sum/$num;
                    146:     } else {
                    147:        return undef;
                    148:     }   
                    149: }
                    150: 
                    151: sub STDDEV {
                    152:     my $mask=mask(@_);
                    153:     my $sum=0; my $num=0;
                    154:     map {
                    155:         $sum+=$v{$_};
                    156:         $num++;
                    157:     } grep /$mask/,keys %v;
                    158:     unless ($num>1) { return undef; }
                    159:     my $mean=$sum/$num;
                    160:     $sum=0;
                    161:     map {
                    162:         $sum+=($v{$_}-$mean)**2;
                    163:     } grep /$mask/,keys %v;
                    164:     return sqrt($sum/($num-1));    
                    165: }
                    166: 
                    167: sub PROD {
                    168:     my $mask=mask(@_);
                    169:     my $prod=1;
                    170:     map {
                    171:         $prod*=$v{$_};
                    172:     } grep /$mask/,keys %v;
                    173:     return $prod;   
                    174: }
                    175: 
                    176: sub MAX {
                    177:     my $mask=mask(@_);
                    178:     my $max='-';
                    179:     map {
                    180:         unless ($max) { $max=$v{$_}; }
                    181:         if (($v{$_}>$max) || ($max eq '-')) { $max=$v{$_}; }
                    182:     } grep /$mask/,keys %v;
                    183:     return $max;   
                    184: }
                    185: 
                    186: sub MIN {
                    187:     my $mask=mask(@_);
                    188:     my $min='-';
                    189:     map {
                    190:         unless ($max) { $max=$v{$_}; }
                    191:         if (($v{$_}<$min) || ($min eq '-')) { $min=$v{$_}; }
                    192:     } grep /$mask/,keys %v;
                    193:     return $min;   
                    194: }
                    195: 
                    196: sub SUMMAX {
                    197:     my ($num,$lower,$upper)=@_;
                    198:     my $mask=mask($lower,$upper);
                    199:     my @inside=();
                    200:     map {
                    201: 	$inside[$#inside+1]=$v{$_};
                    202:     } grep /$mask/,keys %v;
                    203:     @inside=sort(@inside);
                    204:     my $sum=0; my $i;
                    205:     for ($i=$#inside;(($i>$#inside-$num) && ($i>=0));$i--) { 
                    206:         $sum+=$inside[$i];
                    207:     }
                    208:     return $sum;   
                    209: }
                    210: 
                    211: sub SUMMIN {
                    212:     my ($num,$lower,$upper)=@_;
                    213:     my $mask=mask($lower,$upper);
                    214:     my @inside=();
                    215:     map {
                    216: 	$inside[$#inside+1]=$v{$_};
                    217:     } grep /$mask/,keys %v;
                    218:     @inside=sort(@inside);
                    219:     my $sum=0; my $i;
                    220:     for ($i=0;(($i<$num) && ($i<=$#inside));$i++) { 
                    221:         $sum+=$inside[$i];
                    222:     }
                    223:     return $sum;   
                    224: }
                    225: 
                    226: sub sett {
                    227:     %t=();
                    228:     map {
                    229: 	if ($f{$_}) {
                    230: 	    $t{$_}=$f{$_};
                    231:             $t{$_}=~s/\.+/\,/g;
                    232:             $t{$_}=~s/(^|[^\"\'])([A-Z]\d+)/$1\$v\{\'$2\'\}/g;
                    233:         }
                    234:     } keys %f;
                    235: }
                    236: 
1.4     ! www       237: sub calc {
1.1       www       238:     %v=();
1.4     ! www       239:     &sett();
1.1       www       240:     my $notfinished=1;
                    241:     my $depth=0;
                    242:     while ($notfinished) {
                    243: 	$notfinished=0;
                    244:         map {
                    245:             my $old=$v{$_};
1.4     ! www       246:             $v{$_}=eval($t{$_});
1.1       www       247: 	    if ($@) {
                    248: 		%v=();
                    249:                 return $@;
                    250:             }
                    251: 	    if ($v{$_} ne $old) { $notfinished=1; }
                    252:         } keys %t;
                    253:         $depth++;
                    254:         if ($depth>100) {
                    255: 	    %v=();
                    256:             return 'Maximum calculation depth exceeded';
                    257:         }
                    258:     }
                    259:     return '';
                    260: }
                    261: 
1.4     ! www       262: # ------------------------------------------- End of "Inside of the safe space"
        !           263: ENDDEFS
        !           264:     $safeeval->reval($code);
        !           265:     return $safeeval;
        !           266: }
        !           267: 
        !           268: # ------------------------------------------------ Add or change formula values
        !           269: 
        !           270: sub setformulas {
        !           271:     my ($safeeval,@f)=@_;
        !           272:     $safeeval->reval('%f=(%f,'."('".join("','",@f)."'));");
        !           273: }
        !           274: 
        !           275: # ------------------------------------------------------- Calculate spreadsheet
        !           276: 
        !           277: sub calcsheet {
        !           278:     my $safeeval=shift;
        !           279:     $safeeval->reval('&calc();');
        !           280: }
        !           281: 
        !           282: # ------------------------------------------------------------------ Get values
        !           283: 
        !           284: sub getvalues {
        !           285:     my $safeeval=shift;
        !           286:     return $safeeval->reval('%v');
        !           287: }
        !           288: 
        !           289: # ---------------------------------------------------------------- Get formulas
        !           290: 
        !           291: sub getformulas {
        !           292:     my $safeeval=shift;
        !           293:     return $safeeval->reval('%f');
        !           294: }
        !           295: 
1.3       www       296: # ------------------------------------------------------------ Read spreadsheet
                    297: 
                    298: sub readf {
                    299:     my $fn=shift;
1.4     ! www       300:     my %f=();
1.3       www       301:     my $content;
                    302:     {
                    303:       my $fh=Apache::File->new($fn);
                    304:       $content=join('',<$fh>);
                    305:     }
                    306:     {
                    307:       my $parser=HTML::TokeParser->new(\$content);
                    308:       my $token;
                    309:       while ($token=$parser->get_token) {
                    310:          if ($token->[0] eq 'S') {
                    311: 	     if ($token->[1] eq 'field') {
                    312: 		 $f{$token->[2]->{'col'}.$token->[2]->{'row'}}=
                    313: 		     $parser->get_text('/field');
                    314: 	     }
                    315:          }
                    316:       }
                    317:     }
                    318: }
                    319: 
                    320: # --------------------------------------------------------------- Read metadata
                    321: 
                    322: sub readmeta {
                    323:     my $fn=shift;
                    324:     unless ($fn=~/\.meta$/) { $fn.='meta'; }
                    325:     my $content;
                    326:     my %returnhash=();
                    327:     {
                    328:       my $fh=Apache::File->new($fn);
                    329:       $content=join('',<$fh>);
                    330:     }
                    331:    my $parser=HTML::TokeParser->new(\$content);
                    332:    my $token;
                    333:    while ($token=$parser->get_token) {
                    334:       if ($token->[0] eq 'S') {
                    335:          my $entry=$token->[1];
                    336:          if (($entry eq 'stores') || ($entry eq 'parameter')) {
                    337:              my $unikey=$entry;
                    338:              $unikey.='_'.$token->[2]->{'part'}; 
                    339:              $unikey.='_'.$token->[2]->{'name'}; 
                    340:              $returnhash{$unikey}=$token->[2]->{'display'};
                    341:          }
                    342:      }
                    343:   }
                    344:     return %returnhash;
                    345: }
                    346: 
                    347: 
                    348: # -----------------------------------------------------------------------------
                    349: 
                    350: sub handler {
                    351: 
                    352:     my $r=shift;
                    353: 
                    354:   $r->content_type('text/html');
                    355:   $r->send_http_header;
                    356: 
                    357:   $r->print('<html><head><title>LON-CAPA Spreadsheet</title></head>');
                    358:   $r->print('<body bgcolor="#FFFFFF">');
                    359:  
1.4     ! www       360:     my $sheetone=initsheet();
1.3       www       361: 
1.4     ! www       362:     &setformulas($sheetone,('A1' => '5', 'B2' => '6', 'C4' => 'A1+B2'));
        !           363:     $r->print(&calcsheet($sheetone));
        !           364:     my %output=&getformulas($sheetone);
        !           365:     
        !           366:     $r->print('FORM:'.$output{'A1'}.' '.$output{'B2'}.' '.$output{'C4'});
        !           367: 
        !           368:     my %output=&getvalues($sheetone);
        !           369:     
        !           370:     $r->print('<br>OUT:'.$output{'A1'}.' '.$output{'B2'}.' '.$output{'C4'});
1.3       www       371: 
                    372:     $r->print('</body></html>');
                    373:     return OK;
1.1       www       374: }
                    375: 
                    376: 1;
                    377: __END__
                    378: 
                    379: 
                    380: 
                    381: 
                    382: 
                    383: 
                    384: 
                    385: 
                    386: 
                    387: 
                    388: 
                    389: 
                    390: 
                    391: 
                    392: 
                    393: 

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