1: #
2: # $Id: Spreadsheet.pm,v 1.9 2003/05/28 15:20:40 matthew Exp $
3: #
4: # Copyright Michigan State University Board of Trustees
5: #
6: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
7: #
8: # LON-CAPA is free software; you can redistribute it and/or modify
9: # it under the terms of the GNU General Public License as published by
10: # the Free Software Foundation; either version 2 of the License, or
11: # (at your option) any later version.
12: #
13: # LON-CAPA is distributed in the hope that it will be useful,
14: # but WITHOUT ANY WARRANTY; without even the implied warranty of
15: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16: # GNU General Public License for more details.
17: #
18: # You should have received a copy of the GNU General Public License
19: # along with LON-CAPA; if not, write to the Free Software
20: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21: #
22: # /home/httpd/html/adm/gpl.txt
23: #
24: # http://www.lon-capa.org/
25: #
26: # The LearningOnline Network with CAPA
27: # Spreadsheet/Grades Display Handler
28: #
29: # POD required stuff:
30:
31: =head1 NAME
32:
33: Spreadsheet
34:
35: =head1 SYNOPSIS
36:
37: =head1 DESCRIPTION
38:
39: =over 4
40:
41: =cut
42:
43: ###################################################
44: ###################################################
45: ### Spreadsheet ###
46: ###################################################
47: ###################################################
48: package Apache::Spreadsheet;
49:
50: use strict;
51: use Apache::Constants qw(:common :http);
52: use Apache::lonnet;
53: use Safe;
54: use Safe::Hole;
55: use Opcode;
56: use HTML::Entities();
57: use HTML::TokeParser;
58: use Spreadsheet::WriteExcel;
59: use Time::HiRes;
60:
61: ##
62: ## Package Variables
63: ##
64: my %expiredates;
65:
66: my @UC_Columns = split(//,'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
67: my @LC_Columns = split(//,'abcdefghijklmnopqrstuvwxyz');
68:
69: ######################################################
70:
71: =pod
72:
73: =item &new
74:
75: Returns a new spreadsheet object.
76:
77: =cut
78:
79: ######################################################
80: sub new {
81: my $this = shift;
82: my $class = ref($this) || $this;
83: my ($stype) = ($class =~ /Apache::(.*)$/);
84: #
85: my ($name,$domain,$filename,$usymb)=@_;
86: #
87: my $self = {
88: name => $name,
89: domain => $domain,
90: type => $stype,
91: symb => $usymb,
92: errorlog => '',
93: maxrow => '',
94: cid => $ENV{'request.course.id'},
95: cnum => $ENV{'course.'.$ENV{'request.course.id'}.'.num'},
96: cdom => $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
97: chome => $ENV{'course.'.$ENV{'request.course.id'}.'.home'},
98: coursedesc => $ENV{'course.'.$ENV{'request.course.id'}.'.description'},
99: coursefilename => $ENV{'request.course.fn'},
100: temporary => '',
101: #
102: # blackout is used to determine if any data needs to be hidden from the
103: # student.
104: blackout => 0,
105: #
106: # Data storage
107: formulas => {},
108: constants => {},
109: rows => [],
110: row_source => {},
111: othersheets => [],
112: };
113: #
114: $self->{'uhome'} = &Apache::lonnet::homeserver($name,$domain);
115: #
116: bless($self,$class);
117: #
118: # Load in the spreadsheet definition
119: $self->filename($filename);
120: if (exists($ENV{'form.workcopy'}) &&
121: $self->{'type'} eq $ENV{'form.workcopy'}) {
122: $self->load_tmp();
123: } else {
124: $self->load();
125: }
126: return $self;
127: }
128:
129: ######################################################
130:
131: =pod
132:
133: =item &filename
134:
135: get or set the filename for a spreadsheet.
136:
137: =cut
138:
139: ######################################################
140: sub filename {
141: my $self = shift();
142: if (@_) {
143: my ($newfilename) = @_;
144: if (! defined($newfilename) || $newfilename eq 'Default' ||
145: $newfilename !~ /\w/) {
146: my %tmphash = &Apache::lonnet::get('environment',
147: ['spreadsheet_default_'.
148: $self->{'type'}],
149: $self->{'cdom'},
150: $self->{'cnum'});
151: my ($tmp) = keys(%tmphash);
152: if ($tmp !~ /^(con_lost|error|no_such_host)/i) {
153: $newfilename=$tmphash{'spreadsheet_default_'.$self->{'type'}};
154: }
155: if (! defined($newfilename) ||
156: $newfilename !~ /\w/ ||
157: $newfilename =~ /^\W*$/) {
158: $newfilename = 'default.'.$self->{'type'};
159: }
160: } elsif ($newfilename !~ /^\/res\/.*\.spreadsheet/ &&
161: $newfilename !~ /^default\.$self->{'type'}$/ ) {
162: my $regexp = '_'.$self->{'type'}.'$';
163: if ($newfilename !~ /$regexp/) {
164: $newfilename .= '_'.$self->{'type'};
165: }
166: }
167: $self->{'filename'} = $newfilename;
168: return;
169: }
170: return $self->{'filename'};
171: }
172:
173: ######################################################
174:
175: =pod
176:
177: =item &make_default()
178:
179: Make the current spreadsheet file the default for the course. Expires all the
180: default spreadsheets.......!
181:
182: =cut
183:
184: ######################################################
185: sub make_default {
186: my $self = shift();
187: my $result = &Apache::lonnet::put('environment',
188: {'spreadsheet_default_'.$self->{'type'} => $self->filename()},
189: $self->{'cdom'},$self->{'cnum'});
190: return $result if ($result ne 'ok');
191: my $symb = $self->{'symb'};
192: $symb = '' if (! defined($symb));
193: &Apache::lonnet::expirespread('','',$self->{'type'},$symb);
194: }
195:
196: ######################################################
197:
198: =pod
199:
200: =item &is_default()
201:
202: Returns 1 if the current spreadsheet is the default as specified in the
203: course environment. Returns 0 otherwise.
204:
205: =cut
206:
207: ######################################################
208: sub is_default {
209: my $self = shift;
210: # Check to find out if we are the default spreadsheet (filenames match)
211: my $default_filename = '';
212: my %tmphash = &Apache::lonnet::get('environment',
213: ['spreadsheet_default_'.
214: $self->{'type'}],
215: $self->{'cdom'},
216: $self->{'cnum'});
217: my ($tmp) = keys(%tmphash);
218: if ($tmp !~ /^(con_lost|error|no_such_host)/i) {
219: $default_filename = $tmphash{'spreadsheet_default_'.$self->{'type'}};
220: }
221: return 1 if ($self->filename() eq $default_filename);
222: return 0;
223: }
224:
225: sub initialize_spreadsheet_package {
226: &load_spreadsheet_expirationdates();
227: &clear_spreadsheet_definition_cache();
228: }
229:
230: sub load_spreadsheet_expirationdates {
231: undef %expiredates;
232: my $cid=$ENV{'request.course.id'};
233: my @tmp = &Apache::lonnet::dump('nohist_expirationdates',
234: $ENV{'course.'.$cid.'.domain'},
235: $ENV{'course.'.$cid.'.num'});
236: if (lc($tmp[0]) !~ /^error/){
237: %expiredates = @tmp;
238: }
239: }
240:
241: sub check_expiration_time {
242: my $self = shift;
243: my ($time)=@_;
244: my ($key1,$key2,$key3,$key4);
245: $key1 = '::'.$self->{'type'}.':';
246: $key2 = $self->{'name'}.':'.$self->{'domain'}.':'.$self->{'type'}.':';
247: $key3 = $key2.$self->{'container'} if (defined($self->{'container'}));
248: $key4 = $key2.$self->{'usymb'} if (defined($self->{'usymb'}));
249: foreach my $key ($key1,$key2,$key3,$key4) {
250: next if (! defined($key));
251: if (exists($expiredates{$key}) &&$expiredates{$key} > $time) {
252: return 0;
253: }
254: }
255: return 1;
256: }
257:
258: ######################################################
259:
260: =pod
261:
262: =item &initialize_safe_space
263:
264: Returns the safe space required by a Spreadsheet object.
265:
266: =head 2 Safe Space Functions
267:
268: =over 4
269:
270: =cut
271:
272: ######################################################
273: sub initialize_safe_space {
274: my $self = shift;
275: my $safeeval = new Safe(shift);
276: my $safehole = new Safe::Hole;
277: $safeeval->permit("entereval");
278: $safeeval->permit(":base_math");
279: $safeeval->permit("sort");
280: $safeeval->deny(":base_io");
281: $safehole->wrap(\&Apache::lonnet::EXT,$safeeval,'&EXT');
282: $safehole->wrap(\&mask,$safeeval,'&mask');
283: $safeeval->share('$@');
284: my $code=<<'ENDDEFS';
285: # ---------------------------------------------------- Inside of the safe space
286: #
287: # f: formulas
288: # t: intermediate format (variable references expanded)
289: # v: output values
290: # c: preloaded constants (A-column)
291: # rl: row label
292: # os: other spreadsheets (for student spreadsheet only)
293: undef %sheet_values; # Holds the (computed, final) values for the sheet
294: # This is only written to by &calc, the spreadsheet computation routine.
295: # It is read by many functions
296: undef %t; # Holds the values of the spreadsheet temporarily. Set in &sett,
297: # which does the translation of strings like C5 into the value in C5.
298: # Used in &calc - %t holds the values that are actually eval'd.
299: undef %f; # Holds the formulas for each cell. This is the users
300: # (spreadsheet authors) data for each cell.
301: undef %c; # Holds the constants for a sheet. In the assessment
302: # sheets, this is the A column. Used in &MINPARM, &MAXPARM, &expandnamed,
303: # &sett, and &constants. There is no &getconstants.
304: # &constants is called by &loadstudent, &loadcourse, &load assessment,
305: undef @os; # Holds the names of other spreadsheets - this is used to specify
306: # the spreadsheets that are available for the assessment sheet.
307: # Set by &setothersheets. &setothersheets is called by &handler. A
308: # related subroutine is &othersheets.
309: $errorlog = '';
310: #
311: $maxrow = 0;
312: $type = '';
313: #
314: # filename/reference of the sheet
315: $filename = '';
316: #
317: # user data
318: $name = '';
319: $uhome = '';
320: $domain = '';
321: #
322: # course data
323: $csec = '';
324: $chome= '';
325: $cnum = '';
326: $cdom = '';
327: $cid = '';
328: $coursefilename = '';
329: #
330: # symb
331: $usymb = '';
332: #
333: # error messages
334: $errormsg = '';
335: #
336: #-------------------------------------------------------
337:
338: =pod
339:
340: =item NUM(range)
341:
342: returns the number of items in the range.
343:
344: =cut
345:
346: #-------------------------------------------------------
347: sub NUM {
348: my $mask=&mask(@_);
349: my $num= $#{@{grep(/$mask/,keys(%sheet_values))}}+1;
350: return $num;
351: }
352:
353: #-------------------------------------------------------
354:
355: =pod
356:
357: =item BIN(low,high,lower,upper)
358:
359: =cut
360:
361: #-------------------------------------------------------
362: sub BIN {
363: my ($low,$high,$lower,$upper)=@_;
364: my $mask=&mask($lower,$upper);
365: my $num=0;
366: foreach (grep /$mask/,keys(%sheet_values)) {
367: if (($sheet_values{$_}>=$low) && ($sheet_values{$_}<=$high)) {
368: $num++;
369: }
370: }
371: return $num;
372: }
373:
374: #-------------------------------------------------------
375:
376: =pod
377:
378: =item SUM(range)
379:
380: returns the sum of items in the range.
381:
382: =cut
383:
384: #-------------------------------------------------------
385: sub SUM {
386: my $mask=&mask(@_);
387: my $sum=0;
388: foreach (grep /$mask/,keys(%sheet_values)) {
389: $sum+=$sheet_values{$_};
390: }
391: return $sum;
392: }
393:
394: #-------------------------------------------------------
395:
396: =pod
397:
398: =item MEAN(range)
399:
400: compute the average of the items in the range.
401:
402: =cut
403:
404: #-------------------------------------------------------
405: sub MEAN {
406: my $mask=&mask(@_);
407: my $sum=0;
408: my $num=0;
409: foreach (grep /$mask/,keys(%sheet_values)) {
410: $sum+=$sheet_values{$_};
411: $num++;
412: }
413: if ($num) {
414: return $sum/$num;
415: } else {
416: return undef;
417: }
418: }
419:
420: #-------------------------------------------------------
421:
422: =pod
423:
424: =item STDDEV(range)
425:
426: compute the standard deviation of the items in the range.
427:
428: =cut
429:
430: #-------------------------------------------------------
431: sub STDDEV {
432: my $mask=&mask(@_);
433: my $sum=0; my $num=0;
434: foreach (grep /$mask/,keys(%sheet_values)) {
435: $sum+=$sheet_values{$_};
436: $num++;
437: }
438: unless ($num>1) { return undef; }
439: my $mean=$sum/$num;
440: $sum=0;
441: foreach (grep /$mask/,keys(%sheet_values)) {
442: $sum+=($sheet_values{$_}-$mean)**2;
443: }
444: return sqrt($sum/($num-1));
445: }
446:
447: #-------------------------------------------------------
448:
449: =pod
450:
451: =item PROD(range)
452:
453: compute the product of the items in the range.
454:
455: =cut
456:
457: #-------------------------------------------------------
458: sub PROD {
459: my $mask=&mask(@_);
460: my $prod=1;
461: foreach (grep /$mask/,keys(%sheet_values)) {
462: $prod*=$sheet_values{$_};
463: }
464: return $prod;
465: }
466:
467: #-------------------------------------------------------
468:
469: =pod
470:
471: =item MAX(range)
472:
473: compute the maximum of the items in the range.
474:
475: =cut
476:
477: #-------------------------------------------------------
478: sub MAX {
479: my $mask=&mask(@_);
480: my $max='-';
481: foreach (grep /$mask/,keys(%sheet_values)) {
482: unless ($max) { $max=$sheet_values{$_}; }
483: if (($sheet_values{$_}>$max) || ($max eq '-')) {
484: $max=$sheet_values{$_};
485: }
486: }
487: return $max;
488: }
489:
490: #-------------------------------------------------------
491:
492: =pod
493:
494: =item MIN(range)
495:
496: compute the minimum of the items in the range.
497:
498: =cut
499:
500: #-------------------------------------------------------
501: sub MIN {
502: my $mask=&mask(@_);
503: my $min='-';
504: foreach (grep /$mask/,keys(%sheet_values)) {
505: unless ($max) { $max=$sheet_values{$_}; }
506: if (($sheet_values{$_}<$min) || ($min eq '-')) {
507: $min=$sheet_values{$_};
508: }
509: }
510: return $min;
511: }
512:
513: #-------------------------------------------------------
514:
515: =pod
516:
517: =item SUMMAX(num,lower,upper)
518:
519: compute the sum of the largest 'num' items in the range from
520: 'lower' to 'upper'
521:
522: =cut
523:
524: #-------------------------------------------------------
525: sub SUMMAX {
526: my ($num,$lower,$upper)=@_;
527: my $mask=&mask($lower,$upper);
528: my @inside=();
529: foreach (grep /$mask/,keys(%sheet_values)) {
530: push (@inside,$sheet_values{$_});
531: }
532: @inside=sort(@inside);
533: my $sum=0; my $i;
534: for ($i=$#inside;(($i>$#inside-$num) && ($i>=0));$i--) {
535: $sum+=$inside[$i];
536: }
537: return $sum;
538: }
539:
540: #-------------------------------------------------------
541:
542: =pod
543:
544: =item SUMMIN(num,lower,upper)
545:
546: compute the sum of the smallest 'num' items in the range from
547: 'lower' to 'upper'
548:
549: =cut
550:
551: #-------------------------------------------------------
552: sub SUMMIN {
553: my ($num,$lower,$upper)=@_;
554: my $mask=&mask($lower,$upper);
555: my @inside=();
556: foreach (grep /$mask/,keys(%sheet_values)) {
557: $inside[$#inside+1]=$sheet_values{$_};
558: }
559: @inside=sort(@inside);
560: my $sum=0; my $i;
561: for ($i=0;(($i<$num) && ($i<=$#inside));$i++) {
562: $sum+=$inside[$i];
563: }
564: return $sum;
565: }
566:
567: #-------------------------------------------------------
568:
569: =pod
570:
571: =item MINPARM(parametername)
572:
573: Returns the minimum value of the parameters matching the parametername.
574: parametername should be a string such as 'duedate'.
575:
576: =cut
577:
578: #-------------------------------------------------------
579: sub MINPARM {
580: my ($expression) = @_;
581: my $min = undef;
582: study($expression);
583: foreach $parameter (keys(%c)) {
584: next if ($parameter !~ /$expression/);
585: if ((! defined($min)) || ($min > $c{$parameter})) {
586: $min = $c{$parameter}
587: }
588: }
589: return $min;
590: }
591:
592: #-------------------------------------------------------
593:
594: =pod
595:
596: =item MAXPARM(parametername)
597:
598: Returns the maximum value of the parameters matching the input parameter name.
599: parametername should be a string such as 'duedate'.
600:
601: =cut
602:
603: #-------------------------------------------------------
604: sub MAXPARM {
605: my ($expression) = @_;
606: my $max = undef;
607: study($expression);
608: foreach $parameter (keys(%c)) {
609: next if ($parameter !~ /$expression/);
610: if ((! defined($min)) || ($max < $c{$parameter})) {
611: $max = $c{$parameter}
612: }
613: }
614: return $max;
615: }
616:
617:
618: sub calc {
619: %sheet_values = %t;
620: my $notfinished = 1;
621: my $lastcalc = '';
622: my $depth = 0;
623: while ($notfinished) {
624: $notfinished=0;
625: while (my ($cell,$value) = each(%t)) {
626: my $old=$sheet_values{$cell};
627: $sheet_values{$cell}=eval $value;
628: # $errorlog .= $cell.' = '.$old.'->'.$sheet_values{$cell}."\n";
629: if ($@) {
630: undef %sheet_values;
631: return $cell.': '.$@;
632: }
633: if ($sheet_values{$cell} ne $old) {
634: $notfinished=1;
635: $lastcalc=$cell;
636: }
637: }
638: # $errorlog.="------------------------------------------------";
639:
640: $depth++;
641: if ($depth>100) {
642: undef %sheet_values;
643: return $lastcalc.': Maximum calculation depth exceeded';
644: }
645: }
646: return '';
647: }
648:
649: # ------------------------------------------- End of "Inside of the safe space"
650: ENDDEFS
651: $safeeval->reval($code);
652: $self->{'safe'} = $safeeval;
653: $self->{'root'} = $self->{'safe'}->root();
654: #
655: # Place some of the %$self items into the safe space except the safe space
656: # itself
657: my $initstring = '';
658: foreach (qw/name domain type usymb cid csec coursefilename
659: cnum cdom chome uhome/) {
660: $initstring.= qq{\$$_="$self->{$_}";};
661: }
662: $self->{'safe'}->reval($initstring);
663: return $self;
664: }
665: ######################################################
666:
667: =pod
668:
669: =back
670:
671: =cut
672:
673: ######################################################
674:
675:
676: ######################################################
677:
678:
679: ######################################################
680: {
681:
682: my %memoizer;
683:
684: sub mask {
685: my ($lower,$upper)=@_;
686: my $key = $lower.'_'.$upper;
687: if (exists($memoizer{$key})) {
688: return $memoizer{$key};
689: }
690: $upper = $lower if (! defined($upper));
691: #
692: my ($la,$ld) = ($lower=~/([A-Za-z]|\*)(\d+|\*)/);
693: my ($ua,$ud) = ($upper=~/([A-Za-z]|\*)(\d+|\*)/);
694: #
695: my $alpha='';
696: my $num='';
697: #
698: if (($la eq '*') || ($ua eq '*')) {
699: $alpha='[A-Za-z]';
700: } else {
701: if (($la=~/[A-Z]/) && ($ua=~/[A-Z]/) ||
702: ($la=~/[a-z]/) && ($ua=~/[a-z]/)) {
703: $alpha='['.$la.'-'.$ua.']';
704: } else {
705: $alpha='['.$la.'-Za-'.$ua.']';
706: }
707: }
708: if (($ld eq '*') || ($ud eq '*')) {
709: $num='\d+';
710: } else {
711: if (length($ld)!=length($ud)) {
712: $num.='(';
713: foreach ($ld=~m/\d/g) {
714: $num.='['.$_.'-9]';
715: }
716: if (length($ud)-length($ld)>1) {
717: $num.='|\d{'.(length($ld)+1).','.(length($ud)-1).'}';
718: }
719: $num.='|';
720: foreach ($ud=~m/\d/g) {
721: $num.='[0-'.$_.']';
722: }
723: $num.=')';
724: } else {
725: my @lda=($ld=~m/\d/g);
726: my @uda=($ud=~m/\d/g);
727: my $i;
728: my $j=0;
729: my $notdone=1;
730: for ($i=0;($i<=$#lda)&&($notdone);$i++) {
731: if ($lda[$i]==$uda[$i]) {
732: $num.=$lda[$i];
733: $j=$i;
734: } else {
735: $notdone=0;
736: }
737: }
738: if ($j<$#lda-1) {
739: $num.='('.$lda[$j+1];
740: for ($i=$j+2;$i<=$#lda;$i++) {
741: $num.='['.$lda[$i].'-9]';
742: }
743: if ($uda[$j+1]-$lda[$j+1]>1) {
744: $num.='|['.($lda[$j+1]+1).'-'.($uda[$j+1]-1).']\d{'.
745: ($#lda-$j-1).'}';
746: }
747: $num.='|'.$uda[$j+1];
748: for ($i=$j+2;$i<=$#uda;$i++) {
749: $num.='[0-'.$uda[$i].']';
750: }
751: $num.=')';
752: } else {
753: if ($lda[-1]!=$uda[-1]) {
754: $num.='['.$lda[-1].'-'.$uda[-1].']';
755: }
756: }
757: }
758: }
759: my $expression ='^'.$alpha.$num."\$";
760: $memoizer{$key} = $expression;
761: return $expression;
762: }
763:
764: }
765:
766: ##
767: ## sub add_hash_to_safe {} # spreadsheet, would like to destroy
768: ##
769:
770: #
771: # expandnamed used to reside in the safe space
772: #
773: sub expandnamed {
774: my $self = shift;
775: my $expression=shift;
776: if ($expression=~/^\&/) {
777: my ($func,$var,$formula)=($expression=~/^\&(\w+)\(([^\;]+)\;(.*)\)/);
778: my @vars=split(/\W+/,$formula);
779: my %values=();
780: foreach my $varname ( @vars ) {
781: if ($varname=~/\D/) {
782: $formula=~s/$varname/'$c{\''.$varname.'\'}'/ge;
783: $varname=~s/$var/\([\\w:\\- ]\+\)/g;
784: foreach (keys(%{$self->{'constants'}})) {
785: if ($_=~/$varname/) {
786: $values{$1}=1;
787: }
788: }
789: }
790: }
791: if ($func eq 'EXPANDSUM') {
792: my $result='';
793: foreach (keys(%values)) {
794: my $thissum=$formula;
795: $thissum=~s/$var/$_/g;
796: $result.=$thissum.'+';
797: }
798: $result=~s/\+$//;
799: return $result;
800: } else {
801: return 0;
802: }
803: } else {
804: # it is not a function, so it is a parameter name
805: # We should do the following:
806: # 1. Take the list of parameter names
807: # 2. look through the list for ones that match the parameter we want
808: # 3. If there are no collisions, return the one that matches
809: # 4. If there is a collision, return 'bad parameter name error'
810: my $returnvalue = '';
811: my @matches = ();
812: $#matches = -1;
813: study $expression;
814: my $parameter;
815: foreach $parameter (keys(%{$self->{'constants'}})) {
816: push @matches,$parameter if ($parameter =~ /$expression/);
817: }
818: if (scalar(@matches) == 0) {
819: $returnvalue = 'unmatched parameter: '.$parameter;
820: } elsif (scalar(@matches) == 1) {
821: # why do we not do this lookup here, instead of delaying it?
822: $returnvalue = '$c{\''.$matches[0].'\'}';
823: } elsif (scalar(@matches) > 0) {
824: # more than one match. Look for a concise one
825: $returnvalue = "'non-unique parameter name : $expression'";
826: foreach (@matches) {
827: if (/^$expression$/) {
828: # why do we not do this lookup here?
829: $returnvalue = '$c{\''.$_.'\'}';
830: }
831: }
832: } else {
833: # There was a negative number of matches, which indicates
834: # something is wrong with reality. Better warn the user.
835: $returnvalue = 'bizzare parameter: '.$parameter;
836: }
837: return $returnvalue;
838: }
839: }
840:
841: sub sett {
842: my $self = shift;
843: my %t=();
844: #
845: # Deal with the template row
846: foreach my $col ($self->template_cells()) {
847: next if ($col=~/^[A-Z]/);
848: foreach my $row ($self->rows()) {
849: # Get the name of this cell
850: my $cell=$col.$row;
851: # Grab the template declaration
852: $t{$cell}=$self->formula('template_'.$col);
853: # Replace '#' with the row number
854: $t{$cell}=~s/\#/$row/g;
855: # Replace '....' with ','
856: $t{$cell}=~s/\.\.+/\,/g;
857: # Replace 'A0' with the value from 'A0'
858: $t{$cell}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g;
859: # Replace parameters
860: $t{$cell}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.$self->expandnamed($2)/ge;
861: }
862: }
863: #
864: # Deal with the normal cells
865: while (my($cell,$formula) = each(%{$self->{'formulas'}})) {
866: next if ($_=~/^template\_/);
867: my ($col,$row) = ($cell =~ /^([A-z])(\d+)$/);
868: if ($row eq '0') {
869: $t{$cell}=$formula;
870: $t{$cell}=~s/\.\.+/\,/g;
871: $t{$cell}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g;
872: $t{$cell}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.$self->expandnamed($2)/ge;
873: } elsif ( $col =~ /^[A-Z]$/ ) {
874: if ($formula !~ /^\!/ && exists($self->{'constants'}->{$cell})) {
875: my $data = $self->{'constants'}->{$cell};
876: $t{$cell} = $data;
877: }
878: } else { # $row > 1 and $col =~ /[a-z]
879: $t{$cell}=$formula;
880: $t{$cell}=~s/\.\.+/\,/g;
881: $t{$cell}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g;
882: $t{$cell}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.$self->expandnamed($2)/ge;
883: }
884: }
885: %{$self->{'safe'}->varglob('t')}=%t;
886: }
887:
888: ##
889: ## sync_safe_space: Called by calcsheet to make sure all the data we
890: # need to calculate is placed into the safe space
891: ##
892: sub sync_safe_space {
893: my $self = shift;
894: # Inside the safe space 'formulas' has a diabolical alter-ego named 'f'.
895: %{$self->{'safe'}->varglob('f')}=%{$self->{'formulas'}};
896: # 'constants' leads a peaceful hidden life of 'c'.
897: %{$self->{'safe'}->varglob('c')}=%{$self->{'constants'}};
898: # 'othersheets' hides as 'os', a disguise few can penetrate.
899: @{$self->{'safe'}->varglob('os')}=@{$self->{'othersheets'}};
900: }
901:
902: ##
903: ## Retrieve the error log from the safe space (used for debugging)
904: ##
905: sub get_errorlog {
906: my $self = shift;
907: $self->{'errorlog'} = $ { $self->{'safe'}->varglob('errorlog') };
908: return $self->{'errorlog'};
909: }
910:
911: ##
912: ## Clear the error log inside the safe space
913: ##
914: sub clear_errorlog {
915: my $self = shift;
916: $ {$self->{'safe'}->varglob('errorlog')} = '';
917: $self->{'errorlog'} = '';
918: }
919:
920: ##
921: ## constants: either set or get the constants
922: ##
923: sub constants {
924: my $self=shift;
925: my ($constants) = @_;
926: if (defined($constants)) {
927: if (! ref($constants)) {
928: my %tmp = @_;
929: $constants = \%tmp;
930: }
931: $self->{'constants'} = $constants;
932: return;
933: } else {
934: return %{$self->{'constants'}};
935: }
936: }
937:
938: ##
939: ## formulas: either set or get the formulas
940: ##
941: sub formulas {
942: my $self=shift;
943: my ($formulas) = @_;
944: if (defined($formulas)) {
945: if (! ref($formulas)) {
946: my %tmp = @_;
947: $formulas = \%tmp;
948: }
949: $self->{'formulas'} = $formulas;
950: $self->{'rows'} = [];
951: $self->{'template_cells'} = [];
952: return;
953: } else {
954: return %{$self->{'formulas'}};
955: }
956: }
957:
958: sub set_formula {
959: my $self = shift;
960: my ($cell,$formula) = @_;
961: $self->{'formulas'}->{$cell}=$formula;
962: return;
963: }
964:
965: ##
966: ## formulas_keys: Return the keys to the formulas hash.
967: ##
968: sub formulas_keys {
969: my $self = shift;
970: my @keys = keys(%{$self->{'formulas'}});
971: return keys(%{$self->{'formulas'}});
972: }
973:
974: ##
975: ## formula: Return the formula for a given cell in the spreadsheet
976: ## returns '' if the cell does not have a formula or does not exist
977: ##
978: sub formula {
979: my $self = shift;
980: my $cell = shift;
981: if (defined($cell) && exists($self->{'formulas'}->{$cell})) {
982: return $self->{'formulas'}->{$cell};
983: }
984: return '';
985: }
986:
987: ##
988: ## logthis: write the input to lonnet.log
989: ##
990: sub logthis {
991: my $self = shift;
992: my $message = shift;
993: &Apache::lonnet::logthis($self->{'type'}.':'.
994: $self->{'name'}.':'.$self->{'domain'}.':'.
995: $message);
996: return;
997: }
998:
999: ##
1000: ## dump_formulas_to_log: makes lonnet.log huge...
1001: ##
1002: sub dump_formulas_to_log {
1003: my $self =shift;
1004: $self->logthis("Spreadsheet formulas");
1005: $self->logthis("--------------------------------------------------------");
1006: while (my ($cell, $formula) = each(%{$self->{'formulas'}})) {
1007: $self->logthis(' '.$cell.' = '.$formula);
1008: }
1009: $self->logthis("--------------------------------------------------------");}
1010:
1011: ##
1012: ## value: returns the computed value of a particular cell
1013: ##
1014: sub value {
1015: my $self = shift;
1016: my $cell = shift;
1017: if (defined($cell) && exists($self->{'values'}->{$cell})) {
1018: return $self->{'values'}->{$cell};
1019: }
1020: return '';
1021: }
1022:
1023: ##
1024: ## dump_values_to_log: makes lonnet.log huge...
1025: ##
1026: sub dump_values_to_log {
1027: my $self =shift;
1028: $self->logthis("Spreadsheet Values");
1029: $self->logthis("------------------------------------------------------");
1030: while (my ($cell, $value) = each(%{$self->{'values'}})) {
1031: $self->logthis(' '.$cell.' = '.$value);
1032: }
1033: $self->logthis("------------------------------------------------------");
1034: }
1035:
1036: ##
1037: ## Yet another debugging function
1038: ##
1039: sub dump_hash_to_log {
1040: my $self= shift();
1041: my %tmp = @_;
1042: if (@_<2) {
1043: %tmp = %{$_[0]};
1044: }
1045: $self->logthis('---------------------------- (begin hash dump)');
1046: while (my ($key,$val) = each (%tmp)) {
1047: $self->logthis(' '.$key.' = '.$val.':');
1048: }
1049: $self->logthis('---------------------------- (finished hash dump)');
1050: }
1051:
1052: ##
1053: ## rebuild_stats: rebuilds the rows and template_cells arrays
1054: ##
1055: sub rebuild_stats {
1056: my $self = shift;
1057: $self->{'rows'}=[];
1058: $self->{'template_cells'}=[];
1059: while (my ($cell,$formula) = each(%{$self->{'formulas'}})) {
1060: push(@{$self->{'rows'}},$1) if ($cell =~ /^A(\d+)/ && $1 != 0);
1061: push(@{$self->{'template_cells'}},$1) if ($cell =~ /^template_(\w+)/);
1062: }
1063: return;
1064: }
1065:
1066: ##
1067: ## template_cells returns a list of the cells defined in the template row
1068: ##
1069: sub template_cells {
1070: my $self = shift;
1071: $self->rebuild_stats() if (! defined($self->{'template_cells'}) ||
1072: ! @{$self->{'template_cells'}});
1073: return @{$self->{'template_cells'}};
1074: }
1075:
1076: ##
1077: ## Sigh....
1078: ##
1079: sub setothersheets {
1080: my $self = shift;
1081: my @othersheets = @_;
1082: $self->{'othersheets'} = \@othersheets;
1083: }
1084:
1085: ##
1086: ## rows returns a list of the names of cells defined in the A column
1087: ##
1088: sub rows {
1089: my $self = shift;
1090: $self->rebuild_stats() if (!@{$self->{'rows'}});
1091: return @{$self->{'rows'}};
1092: }
1093:
1094: #
1095: # calcsheet: makes all the calls to compute the spreadsheet.
1096: #
1097: sub calcsheet {
1098: my $self = shift;
1099: $self->sync_safe_space();
1100: $self->clear_errorlog();
1101: $self->sett();
1102: my $result = $self->{'safe'}->reval('&calc();');
1103: # $self->logthis($self->get_errorlog());
1104: %{$self->{'values'}} = %{$self->{'safe'}->varglob('sheet_values')};
1105: # $self->logthis($self->get_errorlog());
1106: return $result;
1107: }
1108:
1109: ###########################################################
1110: ##
1111: ## Output Helpers
1112: ##
1113: ###########################################################
1114: sub display {
1115: my $self = shift;
1116: my ($r) = @_;
1117: $self->compute($r);
1118: my $outputmode = 'html';
1119: if ($ENV{'form.output_format'} =~ /^(html|excel|csv)$/) {
1120: $outputmode = $ENV{'form.output_format'};
1121: }
1122: if ($outputmode eq 'html') {
1123: $self->outsheet_html($r);
1124: } elsif ($outputmode eq 'excel') {
1125: $self->outsheet_excel($r);
1126: } elsif ($outputmode eq 'csv') {
1127: $self->outsheet_csv($r);
1128: }
1129: return;
1130: }
1131:
1132: ############################################
1133: ## HTML output routines ##
1134: ############################################
1135: sub html_export_row {
1136: my $self = shift();
1137: my $allowed = &Apache::lonnet::allowed('mgr',$ENV{'request.course.id'});
1138: my $row_html;
1139: my @rowdata = $self->get_row(0);
1140: foreach my $cell (@rowdata) {
1141: if ($cell->{'name'} =~ /^[A-Z]/) {
1142: $row_html .= '<td bgcolor="#CCCCFF">'.
1143: &html_editable_cell($cell,'#CCCCFF',$allowed).'</td>';
1144: } else {
1145: $row_html .= '<td bgcolor="#DDCCFF">'.
1146: &html_editable_cell($cell,'#DDCCFF',$allowed).'</td>';
1147: }
1148: }
1149: return $row_html;
1150: }
1151:
1152: sub html_template_row {
1153: my $self = shift();
1154: my $allowed = &Apache::lonnet::allowed('mgr',$ENV{'request.course.id'});
1155: my ($num_uneditable) = @_;
1156: my $row_html;
1157: my @rowdata = $self->get_template_row();
1158: my $count = 0;
1159: for (my $i = 0; $i<=$#rowdata; $i++) {
1160: my $cell = $rowdata[$i];
1161: if ($i < $num_uneditable) {
1162: $row_html .= '<td bgcolor="#FFDDDD">'.
1163: &html_uneditable_cell($cell,'#FFDDDD',$allowed).'</td>';
1164: } else {
1165: $row_html .= '<td bgcolor="#EOFFDD">'.
1166: &html_editable_cell($cell,'#EOFFDD',$allowed).'</td>';
1167: }
1168: }
1169: return $row_html;
1170: }
1171:
1172: sub html_editable_cell {
1173: my ($cell,$bgcolor,$allowed) = @_;
1174: my $result;
1175: my ($name,$formula,$value);
1176: if (defined($cell)) {
1177: $name = $cell->{'name'};
1178: $formula = $cell->{'formula'};
1179: $value = $cell->{'value'};
1180: }
1181: $name = '' if (! defined($name));
1182: $formula = '' if (! defined($formula));
1183: if (! defined($value)) {
1184: $value = '<font color="'.$bgcolor.'">#</font>';
1185: if ($formula ne '') {
1186: $value = '<i>undefined value</i>';
1187: }
1188: } elsif ($value =~ /^\s*$/ ) {
1189: $value = '<font color="'.$bgcolor.'">#</font>';
1190: } else {
1191: $value = &HTML::Entities::encode($value) if ($value !~/ /);
1192: }
1193: return $value if (! $allowed);
1194: # Make the formula safe for outputting
1195: $formula =~ s/\'/\"/g;
1196: # The formula will be parsed by the browser twice before being
1197: # displayed to the user for editing.
1198: $formula = &HTML::Entities::encode(&HTML::Entities::encode($formula));
1199: # Escape newlines so they make it into the edit window
1200: $formula =~ s/\n/\\n/gs;
1201: # Glue everything together
1202: $result .= "<a href=\"javascript:celledit(\'".
1203: $name."','".$formula."');\">".$value."</a>";
1204: return $result;
1205: }
1206:
1207: sub html_uneditable_cell {
1208: my ($cell,$bgcolor) = @_;
1209: my $value = (defined($cell) ? $cell->{'value'} : '');
1210: $value = &HTML::Entities::encode($value) if ($value !~/ /);
1211: return ' '.$value.' ';
1212: }
1213:
1214: sub html_row {
1215: my $self = shift();
1216: my ($num_uneditable,$row) = @_;
1217: my $allowed = &Apache::lonnet::allowed('mgr',$ENV{'request.course.id'});
1218: my @rowdata = $self->get_row($row);
1219: my $num_cols_output = 0;
1220: my $row_html;
1221: foreach my $cell (@rowdata) {
1222: if ($num_cols_output++ < $num_uneditable) {
1223: $row_html .= '<td bgcolor="#FFDDDD">';
1224: $row_html .= &html_uneditable_cell($cell,'#FFDDDD');
1225: } else {
1226: $row_html .= '<td bgcolor="#EOFFDD">';
1227: $row_html .= &html_editable_cell($cell,'#E0FFDD',$allowed);
1228: }
1229: $row_html .= '</td>';
1230: }
1231: return $row_html;
1232: }
1233:
1234: sub html_header {
1235: my $self = shift;
1236: return '' if (! $ENV{'request.role.adv'});
1237: return "<table>\n".
1238: '<tr><th align="center">Output Format</th><tr>'."\n".
1239: '<tr><td>'.&output_selector()."</td></tr>\n".
1240: "</table>\n";
1241: }
1242:
1243: sub output_selector {
1244: my $output_selector = '<select name="output_format" size="3">'."\n";
1245: my $default = 'html';
1246: if (exists($ENV{'form.output_format'})) {
1247: $default = $ENV{'form.output_format'}
1248: } else {
1249: $ENV{'form.output_format'} = $default;
1250: }
1251: foreach (['html','HTML'],
1252: ['excel','Excel'],
1253: ['csv','Comma Seperated Values']) {
1254: my ($name,$description) = @{$_};
1255: $output_selector.=qq{<option value="$name"};
1256: if ($name eq $default) {
1257: $output_selector .= ' selected';
1258: }
1259: $output_selector .= ">$description</option>\n";
1260: }
1261: $output_selector .= "</select>\n";
1262: return $output_selector;
1263: }
1264:
1265: ################################################
1266: ## Excel output routines ##
1267: ################################################
1268: sub excel_output_row {
1269: my $self = shift;
1270: my ($worksheet,$rownum,$rows_output,@prepend) = @_;
1271: my $cols_output = 0;
1272: #
1273: my @rowdata = $self->get_row($rownum);
1274: foreach my $cell (@prepend,@rowdata) {
1275: my $value = $cell;
1276: $value = $cell->{'value'} if (ref($value));
1277: $value =~ s/\ / /gi;
1278: $worksheet->write($rows_output,$cols_output++,$value);
1279: }
1280: return;
1281: }
1282:
1283: sub create_excel_spreadsheet {
1284: my $self = shift;
1285: my ($r) = @_;
1286: my $filename = '/prtspool/'.
1287: $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
1288: time.'_'.rand(1000000000).'.xls';
1289: my $workbook = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);
1290: if (! defined($workbook)) {
1291: $r->log_error("Error creating excel spreadsheet $filename: $!");
1292: $r->print("Problems creating new Excel file. ".
1293: "This error has been logged. ".
1294: "Please alert your LON-CAPA administrator");
1295: return undef;
1296: }
1297: #
1298: # The excel spreadsheet stores temporary data in files, then put them
1299: # together. If needed we should be able to disable this (memory only).
1300: # The temporary directory must be specified before calling 'addworksheet'.
1301: # File::Temp is used to determine the temporary directory.
1302: $workbook->set_tempdir('/home/httpd/perl/tmp');
1303: #
1304: # Determine the name to give the worksheet
1305: return ($workbook,$filename);
1306: }
1307:
1308: sub outsheet_excel {
1309: my $self = shift;
1310: my ($r) = @_;
1311: $r->print("<h2>Preparing Excel Spreadsheet</h2>");
1312: #
1313: # Create excel worksheet
1314: my ($workbook,$filename) = $self->create_excel_spreadsheet($r);
1315: return if (! defined($workbook));
1316: #
1317: # Create main worksheet
1318: my $worksheet = $workbook->addworksheet('main');
1319: my $rows_output = 0;
1320: my $cols_output = 0;
1321: #
1322: # Write excel header
1323: foreach my $value ($self->get_title()) {
1324: $cols_output = 0;
1325: $worksheet->write($rows_output++,$cols_output,$value);
1326: }
1327: $rows_output++; # skip a line
1328: #
1329: # Write summary/export row
1330: $cols_output = 0;
1331: $self->excel_output_row($worksheet,0,$rows_output++,'Summary');
1332: $rows_output++; # skip a line
1333: #
1334: $self->excel_rows($worksheet,$cols_output,$rows_output);
1335: #
1336: #
1337: # Close the excel file
1338: $workbook->close();
1339: #
1340: # Write a link to allow them to download it
1341: $r->print('<br />'.
1342: '<a href="'.$filename.'">Your Excel spreadsheet.</a>'."\n");
1343: return;
1344: }
1345:
1346: #################################
1347: ## CSV output routines ##
1348: #################################
1349: sub outsheet_csv {
1350: my $self = shift;
1351: my ($r) = @_;
1352: my $csvdata = '';
1353: my @Values;
1354: #
1355: # Open the csv file
1356: my $filename = '/prtspool/'.
1357: $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
1358: time.'_'.rand(1000000000).'.csv';
1359: my $file;
1360: unless ($file = Apache::File->new('>'.'/home/httpd'.$filename)) {
1361: $r->log_error("Couldn't open $filename for output $!");
1362: $r->print("Problems occured in writing the csv file. ".
1363: "This error has been logged. ".
1364: "Please alert your LON-CAPA administrator.");
1365: $r->print("<pre>\n".$csvdata."</pre>\n");
1366: return 0;
1367: }
1368: #
1369: # Output the title information
1370: foreach my $value ($self->get_title()) {
1371: print $file "'".&Apache::loncommon::csv_translate($value)."'\n";
1372: }
1373: #
1374: # Output the body of the spreadsheet
1375: $self->csv_rows($file);
1376: #
1377: # Close the csv file
1378: close($file);
1379: $r->print('<br /><br />'.
1380: '<a href="'.$filename.'">Your CSV spreadsheet.</a>'."\n");
1381: #
1382: return 1;
1383: }
1384:
1385: sub csv_output_row {
1386: my $self = shift;
1387: my ($filehandle,$rownum,@prepend) = @_;
1388: #
1389: my @rowdata = ();
1390: if (defined($rownum)) {
1391: @rowdata = $self->get_row($rownum);
1392: }
1393: my @output = ();
1394: foreach my $cell (@prepend,@rowdata) {
1395: my $value = $cell;
1396: $value = $cell->{'value'} if (ref($value));
1397: $value =~ s/\ / /gi;
1398: $value = "'".$value."'";
1399: push (@output,$value);
1400: }
1401: print $filehandle join(',',@output )."\n";
1402: return;
1403: }
1404:
1405: ############################################
1406: ## XML output routines ##
1407: ############################################
1408: sub outsheet_xml {
1409: my $self = shift;
1410: my ($r) = @_;
1411: ## Someday XML
1412: ## Will be rendered for the user
1413: ## But not on this day
1414: my $Str = '<spreadsheet type="'.$self->{'type'}.'">'."\n";
1415: while (my ($cell,$formula) = each(%{$self->{'formulas'}})) {
1416: if ($cell =~ /^template_(\d+)/) {
1417: my $col = $1;
1418: $Str .= '<template col="'.$col.'">'.$formula.'</template>'."\n";
1419: } else {
1420: my ($row,$col) = ($cell =~ /^([A-z])(\d+)/);
1421: next if (! defined($row) || ! defined($col));
1422: $Str .= '<field row="'.$row.'" col="'.$col.'" >'.$formula.'</cell>'
1423: ."\n";
1424: }
1425: }
1426: $Str.="</spreadsheet>";
1427: return $Str;
1428: }
1429:
1430: ############################################
1431: ### Filesystem routines ###
1432: ############################################
1433: sub parse_sheet {
1434: # $sheetxml is a scalar reference or a scalar
1435: my ($sheetxml) = @_;
1436: if (! ref($sheetxml)) {
1437: my $tmp = $sheetxml;
1438: $sheetxml = \$tmp;
1439: }
1440: my %formulas;
1441: my %sources;
1442: my $parser=HTML::TokeParser->new($sheetxml);
1443: my $token;
1444: while ($token=$parser->get_token) {
1445: if ($token->[0] eq 'S') {
1446: if ($token->[1] eq 'field') {
1447: my $cell = $token->[2]->{'col'}.$token->[2]->{'row'};
1448: my $source = $token->[2]->{'source'};
1449: my $formula = $parser->get_text('/field');
1450: $formulas{$cell} = $formula;
1451: $sources{$cell} = $source if (defined($source));
1452: $parser->get_text('/field');
1453: }
1454: if ($token->[1] eq 'template') {
1455: $formulas{'template_'.$token->[2]->{'col'}}=
1456: $parser->get_text('/template');
1457: }
1458: }
1459: }
1460: return (\%formulas,\%sources);
1461: }
1462:
1463: {
1464:
1465: my %spreadsheets;
1466:
1467: sub clear_spreadsheet_definition_cache {
1468: undef(%spreadsheets);
1469: }
1470:
1471: sub load {
1472: my $self = shift;
1473: my $includedir = $Apache::lonnet::perlvar{'lonIncludes'};
1474: #
1475: my $stype = $self->{'type'};
1476: my $cnum = $self->{'cnum'};
1477: my $cdom = $self->{'cdom'};
1478: my $chome = $self->{'chome'};
1479: my $filename = $self->{'filename'};
1480: #
1481: my $cachekey = join('_',($cnum,$cdom,$stype,$filename));
1482: #
1483: # see if sheet is cached
1484: my ($formulas);
1485: if (exists($spreadsheets{$cachekey})) {
1486: $formulas = $spreadsheets{$cachekey}->{'formulas'};
1487: } else {
1488: # Not cached, need to read
1489: if (! defined($self->filename())) {
1490: # load in the default defined spreadsheet
1491: my $sheetxml='';
1492: my $fh;
1493: if ($fh=Apache::File->new($includedir.'/default.'.$filename)) {
1494: $sheetxml=join('',<$fh>);
1495: $fh->close();
1496: } else {
1497: # $sheetxml='<field row="0" col="A">"Error"</field>';
1498: $sheetxml='<field row="0" col="A"></field>';
1499: }
1500: ($formulas,undef) = &parse_sheet(\$sheetxml);
1501: } elsif($self->filename() =~ /^\/res\/.*\.spreadsheet$/) {
1502: # Load a spreadsheet definition file
1503: my $sheetxml=&Apache::lonnet::getfile
1504: (&Apache::lonnet::filelocation('',$filename));
1505: if ($sheetxml == -1) {
1506: $sheetxml='<field row="0" col="A">"Error loading spreadsheet '
1507: .$self->filename().'"</field>';
1508: }
1509: ($formulas,undef) = &parse_sheet(\$sheetxml);
1510: } else {
1511: # Load the spreadsheet definition file from the save file
1512: my %tmphash = &Apache::lonnet::dump($self->filename(),$cdom,$cnum);
1513: my ($tmp) = keys(%tmphash);
1514: if ($tmp !~ /^(con_lost|error|no_such_host)/i) {
1515: while (my ($cell,$formula) = each(%tmphash)) {
1516: $formulas->{$cell}=$formula;
1517: }
1518: } else {
1519: # Unable to grab the specified spreadsheet,
1520: # so we get the default ones instead.
1521: $filename = 'default.'.$stype;
1522: $self->filename($filename);
1523: my $sheetxml;
1524: if (my $fh=Apache::File->new($includedir.'/'.$filename)) {
1525: $sheetxml = join('',<$fh>);
1526: $fh->close();
1527: } else {
1528: $sheetxml='<field row="0" col="A">'.
1529: '"Unable to load spreadsheet"</field>';
1530: }
1531: ($formulas,undef) = &parse_sheet(\$sheetxml);
1532: $self->formulas($formulas);
1533: }
1534: }
1535: $cachekey = join('_',($cnum,$cdom,$stype,$filename));
1536: %{$spreadsheets{$cachekey}->{'formulas'}} = %{$formulas};
1537: }
1538: $self->formulas($formulas);
1539: $self->set_row_sources();
1540: $self->set_row_numbers();
1541: }
1542:
1543: sub set_row_sources {
1544: my $self = shift;
1545: while (my ($cell,$value) = each(%{$self->{'formulas'}})) {
1546: next if ($cell !~ /^A(\d+)/ && $1 > 0);
1547: my $row = $1;
1548: $self->{'row_source'}->{$row} = $value;
1549: }
1550: return;
1551: }
1552:
1553: ##
1554: ## exportrow is *not* used to get the export row from a computed sub-sheet.
1555: ##
1556: sub exportrow {
1557: my $self = shift;
1558: my @exportarray;
1559: foreach my $column (@UC_Columns) {
1560: push(@exportarray,$self->value($column.'0'));
1561: }
1562: return @exportarray;
1563: }
1564:
1565: sub save {
1566: my $self = shift;
1567: my ($makedef)=@_;
1568: my $cid=$self->{'cid'};
1569: # If we are saving it, it must not be temporary
1570: $self->temporary(0);
1571: if (&Apache::lonnet::allowed('opa',$cid)) {
1572: my %f=$self->formulas();
1573: my $stype = $self->{'type'};
1574: my $cnum = $self->{'cnum'};
1575: my $cdom = $self->{'cdom'};
1576: my $chome = $self->{'chome'};
1577: my $fn = $self->{'filename'};
1578: # Cache new sheet
1579: $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f);
1580: # Write sheet
1581: foreach (keys(%f)) {
1582: delete($f{$_}) if ($f{$_} eq 'import');
1583: }
1584: my $reply = &Apache::lonnet::put($fn,\%f,$cdom,$cnum);
1585: return $reply if ($reply ne 'ok');
1586: $reply = &Apache::lonnet::put($stype.'_spreadsheets',
1587: {$fn => $ENV{'user.name'}.'@'.$ENV{'user.domain'}},
1588: $cdom,$cnum);
1589: return $reply if ($reply ne 'ok');
1590: if ($makedef) {
1591: $reply = &Apache::lonnet::put('environment',
1592: {'spreadsheet_default_'.$stype => $fn },
1593: $cdom,$cnum);
1594: return $reply if ($reply ne 'ok');
1595: }
1596: if ($self->is_default()) {
1597: &Apache::lonnet::expirespread('','',$self->{'type'},'');
1598: }
1599: return $reply;
1600: }
1601: return 'unauthorized';
1602: }
1603:
1604: } # end of scope for %spreadsheets
1605:
1606: sub save_tmp {
1607: my $self = shift;
1608: my $filename=$ENV{'user.name'}.'_'.
1609: $ENV{'user.domain'}.'_spreadsheet_'.$self->{'usymb'}.'_'.
1610: $self->{'filename'};
1611: $filename=~s/\W/\_/g;
1612: $filename=$Apache::lonnet::tmpdir.$filename.'.tmp';
1613: $self->temporary(1);
1614: my $fh;
1615: if ($fh=Apache::File->new('>'.$filename)) {
1616: my %f = $self->formulas();
1617: while( my ($cell,$formula) = each(%f)) {
1618: next if ($formula eq 'import');
1619: print $fh &Apache::lonnet::escape($cell)."=".
1620: &Apache::lonnet::escape($formula)."\n";
1621: }
1622: $fh->close();
1623: }
1624: }
1625:
1626: sub load_tmp {
1627: my $self = shift;
1628: my $filename=$ENV{'user.name'}.'_'.
1629: $ENV{'user.domain'}.'_spreadsheet_'.$self->{'usymb'}.'_'.
1630: $self->{'filename'};
1631: $filename=~s/\W/\_/g;
1632: $filename=$Apache::lonnet::tmpdir.$filename.'.tmp';
1633: my %formulas = ();
1634: if (my $spreadsheet_file = Apache::File->new($filename)) {
1635: while (<$spreadsheet_file>) {
1636: chomp;
1637: my ($cell,$formula) = split(/=/);
1638: $cell = &Apache::lonnet::unescape($cell);
1639: $formula = &Apache::lonnet::unescape($formula);
1640: $formulas{$cell} = $formula;
1641: }
1642: $spreadsheet_file->close();
1643: }
1644: # flag the sheet as temporary
1645: $self->temporary(1);
1646: $self->formulas(\%formulas);
1647: $self->set_row_sources();
1648: $self->set_row_numbers();
1649: return;
1650: }
1651:
1652: sub temporary {
1653: my $self=shift;
1654: if (@_) {
1655: ($self->{'temporary'})= @_;
1656: }
1657: return $self->{'temporary'};
1658: }
1659:
1660: sub modify_cell {
1661: # studentcalc overrides this
1662: my $self = shift;
1663: my ($cell,$formula) = @_;
1664: if ($cell =~ /([A-z])\-/) {
1665: $cell = 'template_'.$1;
1666: } elsif ($cell !~ /^([A-z](\d+)|template_[A-z])$/) {
1667: return;
1668: }
1669: $self->set_formula($cell,$formula);
1670: $self->rebuild_stats();
1671: return;
1672: }
1673:
1674: ###########################################
1675: # othersheets: Returns the list of other spreadsheets available
1676: ###########################################
1677: sub othersheets {
1678: my $self = shift();
1679: my ($stype) = @_;
1680: $stype = $self->{'type'} if (! defined($stype) || $stype !~ /calc$/);
1681: #
1682: my @alternatives=();
1683: my %results=&Apache::lonnet::dump($stype.'_spreadsheets',
1684: $self->{'cdom'}, $self->{'cnum'});
1685: my ($tmp) = keys(%results);
1686: if ($tmp =~ /^(con_lost|error|no_such_host)/i ) {
1687: @alternatives = ('Default');
1688: } else {
1689: @alternatives = sort (keys(%results));
1690: }
1691: return @alternatives;
1692: }
1693:
1694: sub blackout {
1695: my $self = shift;
1696: $self->{'blackout'} = $_[0] if (@_);
1697: return $self->{'blackout'};
1698: }
1699:
1700: sub get_row {
1701: my $self = shift;
1702: my ($n)=@_;
1703: my @cols=();
1704: foreach my $col (@UC_Columns,@LC_Columns) {
1705: my $cell = $col.$n;
1706: push(@cols,{ name => $cell,
1707: formula => $self->formula($cell),
1708: value => $self->value($cell)});
1709: }
1710: return @cols;
1711: }
1712:
1713: sub get_template_row {
1714: my $self = shift;
1715: my @cols=();
1716: foreach my $col (@UC_Columns,@LC_Columns) {
1717: my $cell = 'template_'.$col;
1718: push(@cols,{ name => $cell,
1719: formula => $self->formula($cell),
1720: value => $self->formula($cell) });
1721: }
1722: return @cols;
1723: }
1724:
1725: sub set_row_numbers {
1726: my $self = shift;
1727: my %f=$self->formulas();
1728: while (my ($cell,$value) = each(%{$self->{'formulas'}})) {
1729: next if ($cell !~ /^A(\d+)$/);
1730: next if (! defined($value));
1731: $self->{'row_numbers'}->{$value} = $1;
1732: }
1733: }
1734:
1735: sub get_row_number_from_key {
1736: my $self = shift;
1737: my ($key) = @_;
1738: if (! exists($self->{'row_numbers'}->{$key}) ||
1739: ! defined($self->{'row_numbers'}->{$key})) {
1740: # I used to set $f here to the new value, but the key passed for lookup
1741: # may not be the key we need to save
1742: $self->{'maxrow'}++;
1743: $self->{'row_numbers'}->{$key} = $self->{'maxrow'};
1744: }
1745: return $self->{'row_numbers'}->{$key};
1746: }
1747:
1748: 1;
1749:
1750: __END__
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>