Annotation of loncom/interface/lonspreadsheet.pm, revision 1.5
1.1 www 1: # The LearningOnline Network with CAPA
2: # Spreadsheet/Grades Display Handler
3: #
1.5 ! www 4: # 11/11,11/15,11/27,12/04,12/05 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.5 ! www 16: # =============================================================================
! 17: # ===================================== Implements an instance of a spreadsheet
1.4 www 18:
19: sub initsheet {
20: my $safeeval = new Safe;
21: my $safehole = new Safe::Hole;
22: $safeeval->permit("entereval");
23: $safeeval->permit(":base_math");
24: $safeeval->permit("sort");
25: $safeeval->deny(":base_io");
26: $safehole->wrap(\&Apache::lonnet::EXT,$safeeval,'&EXT');
27: my $code=<<'ENDDEFS';
28: # ---------------------------------------------------- Inside of the safe space
29:
1.3 www 30: #
31: # f: formulas
1.4 www 32: # t: intermediate format (variable references expanded)
33: # v: output values
1.3 www 34: #
35:
1.4 www 36: %v=();
37: %t=();
38: %f=();
1.5 ! www 39: $sheettype='';
! 40: $filename='';
1.1 www 41:
42: sub mask {
43: my ($lower,$upper)=@_;
44:
45: $lower=~/([A-Z]|\*)(\d+|\*)/;
46: my $la=$1;
47: my $ld=$2;
48:
49: $upper=~/([A-Z]|\*)(\d+|\*)/;
50: my $ua=$1;
51: my $ud=$2;
52: my $alpha='';
53: my $num='';
54:
55: if (($la eq '*') || ($ua eq '*')) {
56: $alpha='[A-Z]';
57: } else {
58: $alpha='['.$la.'-'.$ua.']';
59: }
60:
61: if (($ld eq '*') || ($ud eq '*')) {
62: $num='\d+';
63: } else {
64: if (length($ld)!=length($ud)) {
65: $num.='(';
66: map {
67: $num.='['.$_.'-9]';
68: } ($ld=~m/\d/g);
69: if (length($ud)-length($ld)>1) {
70: $num.='|\d{'.(length($ld)+1).','.(length($ud)-1).'}';
71: }
72: $num.='|';
73: map {
74: $num.='[0-'.$_.']';
75: } ($ud=~m/\d/g);
76: $num.=')';
77: } else {
78: my @lda=($ld=~m/\d/g);
79: my @uda=($ud=~m/\d/g);
80: my $i; $j=0;
81: for ($i=0;$i<=$#lda;$i++) {
82: if ($lda[$i]==$uda[$i]) {
83: $num.=$lda[$i];
84: $j=$i;
85: }
86: }
87: if ($j<$#lda-1) {
88: $num.='('.$lda[$j+1];
89: for ($i=$j+2;$i<=$#lda;$i++) {
90: $num.='['.$lda[$i].'-9]';
91: }
92: if ($uda[$j+1]-$lda[$j+1]>1) {
93: $num.='|['.($lda[$j+1]+1).'-'.($uda[$j+1]-1).']\d{'.
94: ($#lda-$j-1).'}';
95: }
96: $num.='|'.$uda[$j+1];
97: for ($i=$j+2;$i<=$#uda;$i++) {
98: $num.='[0-'.$uda[$i].']';
99: }
100: $num.=')';
101: } else {
102: $num.='['.$lda[$#lda].'-'.$uda[$#uda].']';
103: }
104: }
105: }
1.4 www 106: return '^'.$alpha.$num."\$";
1.1 www 107: }
108:
109: sub NUM {
110: my $mask=mask(@_);
111: my $num=0;
112: map {
113: $num++;
114: } grep /$mask/,keys %v;
115: return $num;
116: }
117:
118: sub BIN {
119: my ($low,$high,$lower,$upper)=@_;
120: my $mask=mask($lower,$upper);
121: my $num=0;
122: map {
123: if (($v{$_}>=$low) && ($v{$_}<=$high)) {
124: $num++;
125: }
126: } grep /$mask/,keys %v;
127: return $num;
128: }
129:
130:
131: sub SUM {
132: my $mask=mask(@_);
133: my $sum=0;
134: map {
135: $sum+=$v{$_};
136: } grep /$mask/,keys %v;
137: return $sum;
138: }
139:
140: sub MEAN {
141: my $mask=mask(@_);
142: my $sum=0; my $num=0;
143: map {
144: $sum+=$v{$_};
145: $num++;
146: } grep /$mask/,keys %v;
147: if ($num) {
148: return $sum/$num;
149: } else {
150: return undef;
151: }
152: }
153:
154: sub STDDEV {
155: my $mask=mask(@_);
156: my $sum=0; my $num=0;
157: map {
158: $sum+=$v{$_};
159: $num++;
160: } grep /$mask/,keys %v;
161: unless ($num>1) { return undef; }
162: my $mean=$sum/$num;
163: $sum=0;
164: map {
165: $sum+=($v{$_}-$mean)**2;
166: } grep /$mask/,keys %v;
167: return sqrt($sum/($num-1));
168: }
169:
170: sub PROD {
171: my $mask=mask(@_);
172: my $prod=1;
173: map {
174: $prod*=$v{$_};
175: } grep /$mask/,keys %v;
176: return $prod;
177: }
178:
179: sub MAX {
180: my $mask=mask(@_);
181: my $max='-';
182: map {
183: unless ($max) { $max=$v{$_}; }
184: if (($v{$_}>$max) || ($max eq '-')) { $max=$v{$_}; }
185: } grep /$mask/,keys %v;
186: return $max;
187: }
188:
189: sub MIN {
190: my $mask=mask(@_);
191: my $min='-';
192: map {
193: unless ($max) { $max=$v{$_}; }
194: if (($v{$_}<$min) || ($min eq '-')) { $min=$v{$_}; }
195: } grep /$mask/,keys %v;
196: return $min;
197: }
198:
199: sub SUMMAX {
200: my ($num,$lower,$upper)=@_;
201: my $mask=mask($lower,$upper);
202: my @inside=();
203: map {
204: $inside[$#inside+1]=$v{$_};
205: } grep /$mask/,keys %v;
206: @inside=sort(@inside);
207: my $sum=0; my $i;
208: for ($i=$#inside;(($i>$#inside-$num) && ($i>=0));$i--) {
209: $sum+=$inside[$i];
210: }
211: return $sum;
212: }
213:
214: sub SUMMIN {
215: my ($num,$lower,$upper)=@_;
216: my $mask=mask($lower,$upper);
217: my @inside=();
218: map {
219: $inside[$#inside+1]=$v{$_};
220: } grep /$mask/,keys %v;
221: @inside=sort(@inside);
222: my $sum=0; my $i;
223: for ($i=0;(($i<$num) && ($i<=$#inside));$i++) {
224: $sum+=$inside[$i];
225: }
226: return $sum;
227: }
228:
229: sub sett {
230: %t=();
231: map {
232: if ($f{$_}) {
233: $t{$_}=$f{$_};
234: $t{$_}=~s/\.+/\,/g;
235: $t{$_}=~s/(^|[^\"\'])([A-Z]\d+)/$1\$v\{\'$2\'\}/g;
236: }
237: } keys %f;
238: }
239:
1.4 www 240: sub calc {
1.1 www 241: %v=();
1.4 www 242: &sett();
1.1 www 243: my $notfinished=1;
244: my $depth=0;
245: while ($notfinished) {
246: $notfinished=0;
247: map {
248: my $old=$v{$_};
1.4 www 249: $v{$_}=eval($t{$_});
1.1 www 250: if ($@) {
251: %v=();
252: return $@;
253: }
254: if ($v{$_} ne $old) { $notfinished=1; }
255: } keys %t;
256: $depth++;
257: if ($depth>100) {
258: %v=();
259: return 'Maximum calculation depth exceeded';
260: }
261: }
262: return '';
263: }
264:
1.4 www 265: # ------------------------------------------- End of "Inside of the safe space"
266: ENDDEFS
267: $safeeval->reval($code);
268: return $safeeval;
269: }
270:
271: # ------------------------------------------------ Add or change formula values
272:
273: sub setformulas {
274: my ($safeeval,@f)=@_;
275: $safeeval->reval('%f=(%f,'."('".join("','",@f)."'));");
276: }
277:
278: # ------------------------------------------------------- Calculate spreadsheet
279:
280: sub calcsheet {
281: my $safeeval=shift;
282: $safeeval->reval('&calc();');
283: }
284:
285: # ------------------------------------------------------------------ Get values
286:
287: sub getvalues {
288: my $safeeval=shift;
289: return $safeeval->reval('%v');
290: }
291:
292: # ---------------------------------------------------------------- Get formulas
293:
294: sub getformulas {
295: my $safeeval=shift;
296: return $safeeval->reval('%f');
297: }
298:
1.5 ! www 299: # -------------------------------------------------------------------- Set type
! 300:
! 301: sub settype {
! 302: my ($safeeval,$type)=@_;
! 303: $safeeval->reval('$sheettype='.$type.';');
! 304: }
! 305:
! 306: # -------------------------------------------------------------------- Get type
! 307:
! 308: sub gettype {
! 309: my $safeeval=shift;
! 310: return $safeeval->reval('$sheettype');
! 311: }
! 312:
! 313: # -------------------------------------------------------------------- Set type
! 314:
! 315: sub setfilename {
! 316: my ($safeeval,$fn)=@_;
! 317: $safeeval->reval('$filename='.$fn.';');
! 318: }
! 319:
! 320: # -------------------------------------------------------------------- Get type
! 321:
! 322: sub getfilename {
! 323: my $safeeval=shift;
! 324: return $safeeval->reval('$filename');
! 325: }
! 326:
! 327: # ========================================================== End of Spreadsheet
! 328: # =============================================================================
! 329:
! 330:
! 331:
! 332: # --------------------------------------- Read spreadsheet formulas from a file
1.3 www 333:
1.5 ! www 334: sub readsheet {
! 335: my ($safeeval,$fn)=shift;
! 336: &setfilename($safeeval,$fn);
! 337: $fn=~/\.(\w+)/;
! 338: &settype($safeeval,$1);
1.4 www 339: my %f=();
1.3 www 340: my $content;
341: {
342: my $fh=Apache::File->new($fn);
343: $content=join('',<$fh>);
344: }
345: {
346: my $parser=HTML::TokeParser->new(\$content);
347: my $token;
348: while ($token=$parser->get_token) {
349: if ($token->[0] eq 'S') {
350: if ($token->[1] eq 'field') {
351: $f{$token->[2]->{'col'}.$token->[2]->{'row'}}=
352: $parser->get_text('/field');
353: }
354: }
355: }
356: }
1.5 ! www 357: &setformulas($safeeval,%f);
1.3 www 358: }
359:
360: # --------------------------------------------------------------- Read metadata
361:
362: sub readmeta {
363: my $fn=shift;
364: unless ($fn=~/\.meta$/) { $fn.='meta'; }
365: my $content;
366: my %returnhash=();
367: {
368: my $fh=Apache::File->new($fn);
369: $content=join('',<$fh>);
370: }
371: my $parser=HTML::TokeParser->new(\$content);
372: my $token;
373: while ($token=$parser->get_token) {
374: if ($token->[0] eq 'S') {
375: my $entry=$token->[1];
376: if (($entry eq 'stores') || ($entry eq 'parameter')) {
377: my $unikey=$entry;
378: $unikey.='_'.$token->[2]->{'part'};
379: $unikey.='_'.$token->[2]->{'name'};
380: $returnhash{$unikey}=$token->[2]->{'display'};
381: }
382: }
383: }
384: return %returnhash;
385: }
386:
1.5 ! www 387: # ----------------------------------------------------------------- Update rows
! 388:
! 389: sub updaterows {
! 390: my $safeeval=shift;
! 391: my %bighash;
! 392: # -------------------------------------------------------------------- Tie hash
! 393: if (tie(%bighash,'GDBM_File',$ENV{'request.course.fn'}.'.db',
! 394: &GDBM_READER,0640)) {
! 395: # --------------------------------------------------------- Get all assessments
! 396:
! 397: my %allkeys=();
! 398: my %allassess=();
! 399:
! 400: my $stype=&gettype($safeeval);
! 401:
! 402: map {
! 403: if ($_=~/^src\_(\d+)\.(\d+)$/) {
! 404: my $mapid=$1;
! 405: my $resid=$2;
! 406: my $id=$mapid.'.'.$resid;
! 407: my $srcf=$bighash{$_};
! 408: if ($srcf=~/\.(problem|exam|quiz|assess|survey|form)$/) {
! 409: my $symb=
! 410: &Apache::lonnet::declutter($bighash{'map_id_'.$mapid}).
! 411: '___'.$resid.'___'.
! 412: &Apache::lonnet::declutter($srcf);
! 413: $allassess{$symb}=$bighash{'title_'.$id};
! 414: if ($stype eq 'assesssheet') {
! 415: map {
! 416: if ($_=~/^stores\_(.*)/) {
! 417: my $key=$_;
! 418: my $display=
! 419: &Apache::lonnet::metadata($srcf,$key.'.display');
! 420: unless ($display) {
! 421: $display=
! 422: &Apache::lonnet::metadata($srcf,$key.'.name');
! 423: }
! 424: $allkeys{$key}=$display;
! 425: }
! 426: } split(/\,/,&Apache::lonnet::metadata($srcf,'keys'));
! 427: }
! 428: }
! 429: }
! 430: } keys %bighash;
! 431: untie(%bighash);
! 432:
! 433: #
! 434: # %allkeys has a list of storage displays by unikey
! 435: # %allassess has a list of all resource displays by symb
! 436: #
! 437: # -------------------- Find discrepancies between the course row table and this
! 438: #
! 439: my %f=&getformulas($safeeval);
! 440: map {
! 441: if ($_=~/^A/) {
! 442: if ($stype eq 'assesssheet') {
! 443: } elsif ($stype eq 'coursesheet') {
! 444: }
! 445: }
! 446: } keys %f;
! 447: # ------------------------------------------------ Find new and obsolete values
! 448:
! 449: } else {
! 450: return 'Could not access course data';
! 451: }
! 452: }
1.3 www 453:
454: # -----------------------------------------------------------------------------
455:
456: sub handler {
457:
458: my $r=shift;
459:
460: $r->content_type('text/html');
461: $r->send_http_header;
462:
463: $r->print('<html><head><title>LON-CAPA Spreadsheet</title></head>');
464: $r->print('<body bgcolor="#FFFFFF">');
465:
1.4 www 466: my $sheetone=initsheet();
1.3 www 467:
1.4 www 468: &setformulas($sheetone,('A1' => '5', 'B2' => '6', 'C4' => 'A1+B2'));
469: $r->print(&calcsheet($sheetone));
470: my %output=&getformulas($sheetone);
471:
472: $r->print('FORM:'.$output{'A1'}.' '.$output{'B2'}.' '.$output{'C4'});
473:
474: my %output=&getvalues($sheetone);
475:
476: $r->print('<br>OUT:'.$output{'A1'}.' '.$output{'B2'}.' '.$output{'C4'});
1.3 www 477:
478: $r->print('</body></html>');
479: return OK;
1.1 www 480: }
481:
482: 1;
483: __END__
484:
485:
486:
487:
488:
489:
490:
491:
492:
493:
494:
495:
496:
497:
498:
499:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>