--- loncom/interface/spreadsheet/Spreadsheet.pm	2005/04/21 17:29:16	1.42
+++ loncom/interface/spreadsheet/Spreadsheet.pm	2005/09/01 21:47:14	1.55
@@ -1,5 +1,5 @@
 #
-# $Id: Spreadsheet.pm,v 1.42 2005/04/21 17:29:16 albertel Exp $
+# $Id: Spreadsheet.pm,v 1.55 2005/09/01 21:47:14 albertel Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -106,13 +106,13 @@ sub new {
         cid      => $env{'request.course.id'},
         cnum     => $env{'course.'.$env{'request.course.id'}.'.num'},
         cdom     => $env{'course.'.$env{'request.course.id'}.'.domain'},
-        chome    => $env{'course.'.$env{'request.course.id'}.'.home'},
         coursedesc => $env{'course.'.$env{'request.course.id'}.'.description'},
         coursefilename => $env{'request.course.fn'},
         #
         # Flags
         temporary => 0,  # true if this sheet has been modified but not saved
-        new_rows  => 0, # true if this sheet has new rows
+        new_rows  => 0,  # true if this sheet has new rows
+	loaded    => 0,  # true if the formulas have been loaded
         #
         # blackout is used to determine if any data needs to be hidden from the
         # student.
@@ -126,18 +126,9 @@ sub new {
         othersheets => [],
     };
     #
-    $self->{'uhome'} = &Apache::lonnet::homeserver($name,$domain);
-    #
     bless($self,$class);
-    #
-    # Load in the spreadsheet definition
     $self->filename($filename);
-    if (exists($env{'form.workcopy'}) && 
-        $self->{'type'} eq $env{'form.workcopy'}) {
-        $self->load_tmp();
-    } else {
-        $self->load();
-    }
+    #
     return $self;
 }
 
@@ -200,6 +191,8 @@ sub make_default {
             {'spreadsheet_default_'.$self->{'type'} => $self->filename()},
                                      $self->{'cdom'},$self->{'cnum'});
     return $result if ($result ne 'ok');
+    &Apache::lonnet::appenv('course.'.$self->{'cid'}.'.spreadsheet_default_'.
+			    $self->{'type'} => $self->filename());
     my $symb = $self->{'symb'};
     $symb = '' if (! defined($symb));
     &Apache::lonnet::expirespread('','',$self->{'type'},$symb);    
@@ -328,7 +321,12 @@ sub initialize_safe_space {
       $safeeval->deny(":base_io");
       $safehole->wrap(\&Apache::lonnet::EXT,$safeeval,'&Apache::lonnet::EXT');
       $safehole->wrap(\&mask,$safeeval,'&mask');
+      $safehole->wrap(\&Apache::lonnet::logthis,$safeeval,'&logthis');
       $safeeval->share('$@');
+    # Holds the (computed, final) values for the sheet
+    # This is only written to by &calc, the spreadsheet computation routine.
+    # It is read by many functions
+      $safeeval->share('%sheet_values');
       my $code=<<'ENDDEFS';
 # ---------------------------------------------------- Inside of the safe space
 #
@@ -338,12 +336,9 @@ sub initialize_safe_space {
 # c: preloaded constants (A-column)
 # rl: row label
 # os: other spreadsheets (for student spreadsheet only)
-undef %sheet_values;   # Holds the (computed, final) values for the sheet
-    # This is only written to by &calc, the spreadsheet computation routine.
-    # It is read by many functions
-undef %t; # Holds the values of the spreadsheet temporarily. Set in &sett, 
-    # which does the translation of strings like C5 into the value in C5.
-    # Used in &calc - %t holds the values that are actually eval'd.
+undef %t; # Holds the forumlas of the spreadsheet to be computed. Set in
+    # &sett, which does the translation of strings like C5 into the value
+    # in C5. Used in &calc - %t holds the values that are actually eval'd.
 undef %f;    # Holds the formulas for each cell.  This is the users
     # (spreadsheet authors) data for each cell.
 undef %c; # Holds the constants for a sheet.  In the assessment
@@ -364,12 +359,10 @@ $filename = '';
 #
 # user data
 $name = '';
-$uhome = '';
 $domain  = '';
 #
 # course data
 $csec = '';
-$chome= '';
 $cnum = '';
 $cdom = '';
 $cid  = '';
@@ -412,8 +405,8 @@ returns the number of items in the range
 
 #-------------------------------------------------------
 sub NUM {
-    my $mask=&mask(@_);
-    my $num= $#{@{grep(eval("/$mask/"),keys(%sheet_values))}}+1;
+    my $values=&get_values(@_);
+    my $num= scalar(@$values);
     return $num;   
 }
 
@@ -428,10 +421,10 @@ sub NUM {
 #-------------------------------------------------------
 sub BIN {
     my ($low,$high,$lower,$upper)=@_;
-    my $mask=&mask($lower,$upper);
+    my $values=&get_values($lower,$upper);
     my $num=0;
-    foreach (grep eval("/$mask/"),keys(%sheet_values)) {
-        if (($sheet_values{$_}>=$low) && ($sheet_values{$_}<=$high)) {
+    foreach (@$values) {
+        if (($_>=$low) && ($_<=$high)) {
             $num++;
         }
     }
@@ -450,10 +443,10 @@ returns the sum of items in the range.
 
 #-------------------------------------------------------
 sub SUM {
-    my $mask=&mask(@_);
+    my $values=&get_values(@_);
     my $sum=0;
-    foreach (grep eval("/$mask/"),keys(%sheet_values)) {
-        $sum+=$sheet_values{$_};
+    foreach (@$values) {
+        $sum+=$_;
     }
     return $sum;   
 }
@@ -470,11 +463,11 @@ compute the average of the items in the
 
 #-------------------------------------------------------
 sub MEAN {
-    my $mask=&mask(@_);
+    my $values=&get_values(@_);
     my $sum=0; 
     my $num=0;
-    foreach (grep eval("/$mask/"),keys(%sheet_values)) {
-        $sum+=$sheet_values{$_};
+    foreach (@$values) {
+        $sum+=$_;
         $num++;
     }
     if ($num) {
@@ -496,17 +489,17 @@ compute the standard deviation of the it
 
 #-------------------------------------------------------
 sub STDDEV {
-    my $mask=&mask(@_);
+    my $values=&get_values(@_);
     my $sum=0; my $num=0;
-    foreach (grep eval("/$mask/"),keys(%sheet_values)) {
-        $sum+=$sheet_values{$_};
+    foreach (@$values) {
+        $sum+=$_;
         $num++;
     }
     unless ($num>1) { return undef; }
     my $mean=$sum/$num;
     $sum=0;
-    foreach (grep eval("/$mask/"),keys(%sheet_values)) {
-        $sum+=($sheet_values{$_}-$mean)**2;
+    foreach (@$values) {
+        $sum+=($_-$mean)**2;
     }
     return sqrt($sum/($num-1));    
 }
@@ -523,10 +516,10 @@ compute the product of the items in the
 
 #-------------------------------------------------------
 sub PROD {
-    my $mask=&mask(@_);
+    my $values=&get_values(@_);
     my $prod=1;
-    foreach (grep eval("/$mask/"),keys(%sheet_values)) {
-        $prod*=$sheet_values{$_};
+    foreach (@$values) {
+        $prod*=$_;
     }
     return $prod;   
 }
@@ -543,12 +536,11 @@ compute the maximum of the items in the
 
 #-------------------------------------------------------
 sub MAX {
-    my $mask=&mask(@_);
+    my $values=&get_values(@_);
     my $max='-';
-    foreach (grep eval("/$mask/"),keys(%sheet_values)) {
-        unless ($max) { $max=$sheet_values{$_}; }
-        if (($sheet_values{$_}>$max) || ($max eq '-')) { 
-            $max=$sheet_values{$_}; 
+    foreach (@$values) {
+        if (($_>$max) || ($max eq '-')) { 
+            $max=$_; 
         }
     } 
     return $max;   
@@ -566,12 +558,11 @@ compute the minimum of the items in the
 
 #-------------------------------------------------------
 sub MIN {
-    my $mask=&mask(@_);
+    my $values=&get_values(@_);
     my $min='-';
-    foreach (grep eval("/$mask/"),keys(%sheet_values)) {
-        unless ($max) { $max=$sheet_values{$_}; }
-        if (($sheet_values{$_}<$min) || ($min eq '-')) { 
-            $min=$sheet_values{$_}; 
+    foreach (@$values) {
+        if (($_<$min) || ($min eq '-')) { 
+            $min=$_; 
         }
     }
     return $min;   
@@ -591,12 +582,8 @@ compute the sum of the largest 'num' ite
 #-------------------------------------------------------
 sub SUMMAX {
     my ($num,$lower,$upper)=@_;
-    my $mask=&mask($lower,$upper);
-    my @inside=();
-    foreach (grep eval("/$mask/"),keys(%sheet_values)) {
-	push (@inside,$sheet_values{$_});
-    }
-    @inside=sort(@inside);
+    my $values=&get_values($lower,$upper);
+    my @inside=sort {$a <=> $b} (@$values);
     my $sum=0; my $i;
     for ($i=$#inside;(($i>$#inside-$num) && ($i>=0));$i--) { 
         $sum+=$inside[$i];
@@ -618,12 +605,8 @@ compute the sum of the smallest 'num' it
 #-------------------------------------------------------
 sub SUMMIN {
     my ($num,$lower,$upper)=@_;
-    my $mask=&mask($lower,$upper);
-    my @inside=();
-    foreach (grep eval("/$mask/"),keys(%sheet_values)) {
-	$inside[$#inside+1]=$sheet_values{$_};
-    }
-    @inside=sort(@inside);
+    my $values=&get_values($lower,$upper);
+    my @inside=sort {$a <=> $b} (@$values);
     my $sum=0; my $i;
     for ($i=0;(($i<$num) && ($i<=$#inside));$i++) { 
         $sum+=$inside[$i];
@@ -679,9 +662,65 @@ sub MAXPARM {
     return $max;
 }
 
+#-------------------------------------------------------
+
+=pod
+
+=item  &get_values($lower,$upper)
+
+Inputs: $lower and $upper, cell names ("X12" or "a150") or globs ("X*").
+
+Returns: an array ref of the values of the cells that exist in the 
+         speced range
+
+=cut
+
+#-------------------------------------------------------
+sub get_values {
+    my ($lower,$upper)=@_;
+    $upper = $lower if (! defined($upper));
+    my @values;
+    my ($la,$ld) = ($lower=~/([A-z]|\*)(\d+|\*)/);
+    my ($ua,$ud) = ($upper=~/([A-z]|\*)(\d+|\*)/);
+    my ($alpha,$num);
+    if ($ld ne '*' && $ud ne '*') {
+	my @alpha;
+	if (($la eq '*') || ($ua eq '*')) {
+	    @alpha=('A'..'z');
+	} else {
+	    if ($la gt $ua) { ($la,$ua)=($ua,$la); }
+	    if ((lc($la) ne $la) && (lc($ua) eq $ua)) {
+		@alpha=($la..'Z','a'..$ua);
+	    } else {
+		@alpha=($la..$ua);
+            }
+	}
+	my @num=($ld..$ud);
+	foreach my $a (@alpha) {
+	    foreach my $n (@num) {
+		if (exists($sheet_values{$a.$n})) {
+		    push(@values,$sheet_values{$a.$n});
+		}
+	    }
+	}
+	return \@values;
+    } else {
+	$num = '(\d+)';
+    }
+    if (($la eq '*') || ($ua eq '*')) {
+        $alpha='[A-z]';
+    } else {
+	if ($la gt $ua) { ($la,$ua)=($ua,$la); }
+        $alpha=qq/[$la-$ua]/;
+    }
+    my $expression = '^'.$alpha.$num.'$';
+    foreach (grep /$expression/,keys(%sheet_values)) {
+	push(@values,$sheet_values{$_});
+    }
+    return \@values;
+}
 
 sub calc {
-    %sheet_values = %t;
     my $notfinished = 1;
     my $lastcalc = '';
     my $depth = 0;
@@ -722,7 +761,7 @@ ENDDEFS
     # itself
     my $initstring = '';
     foreach (qw/name domain type symb cid csec coursefilename
-             cnum cdom chome uhome/) {
+             cnum cdom/) {
         $initstring.= qq{\$$_="$self->{$_}";};
     }
     $initstring.=qq{\$usection="$usection";};
@@ -742,92 +781,6 @@ ENDDEFS
 
 ######################################################
 
-
-######################################################
-
-=pod
-
-=item  &mask($lower,$upper)
-
-Inputs: $lower and $upper, cell names ("X12" or "a150") or globs ("X*").
-
-Returns:  Regular expression matching spreadsheet cells that are within
-the rectangle defined by $lower and $upper.  Due to the nature of the
-regular expression this result must be used inside an eval().
-
-=cut
-
-######################################################
-{
-
-my %memoizer;
-
-sub mask {
-    my ($lower,$upper)=@_;
-    my $key = $lower.'_'.$upper;
-    if (exists($memoizer{$key})) {
-        return $memoizer{$key};
-    }
-    $upper = $lower if (! defined($upper));
-    #
-    my ($la,$ld) = ($lower=~/([A-z]|\*)(\d+|\*)/);
-    my ($ua,$ud) = ($upper=~/([A-z]|\*)(\d+|\*)/);
-    #
-    my $alpha='';
-    my $num='';
-    #
-    # Do not put parenthases around $alpha.
-    # $num depends on the value in $1.
-    if (($la eq '*') || ($ua eq '*')) {
-        $alpha='[A-z]';
-    } else {
-        if ($la gt $ua) {
-            my $tmp = $ua;
-            $ua = $la;
-            $la = $ua;
-        }
-        $alpha=qq/[$la-$ua]/;
-    }
-    if ($ld ne '*' && $ud ne '*') {
-        # Make sure $ld <= $ud
-        if ($ld > $ud) {
-            my $tmp = $ud;
-            $ud = $ld;
-            $ld = $tmp;
-        }
-        # Here we make a regular expression using some advanced regexp
-        # abilities.
-        # (\d+) will match the digits of the cell name and dump them in
-        #     to $1
-        # (?(?{ ... code ...} pattern_if_true | pattern_if_false)) will
-        #     choose pattern_if_true if { ... code ... } is true and
-        #     pattern_if_false if { ... code ... } is false.
-        # In this case, pattern_if_true is empty.  pattern_if_false is 
-        #     'donotmatch' and will not match our cells because none of 
-        #     them end with donotmatch.  
-        # Unfortunately, the use of this type of regular expression 
-        #     requires that each match be wrapped in an eval().  Search for
-        #     $mask in this module for examples
-        $num = '(\d+)(?(?{$1>= '.$ld.' && $1<='.$ud.'})|donotmatch)';
-    } else {
-        $num = '(\d+)';
-    }
-    my $expression = '^'.$alpha.$num.'$';
-    $memoizer{$key} = $expression;
-    return $expression;
-}
-
-#
-# Debugging routine
-sub dump_memoized_values {
-    while (my ($key,$value) = each(%memoizer)) {
-        &Apache::lonnet::logthis('memoizer: '.$key.' = '.$value);
-    }
-    return;
-}
-
-}
-
 ##
 ## sub add_hash_to_safe {} # spreadsheet, would like to destroy
 ##
@@ -907,6 +860,7 @@ sub expandnamed {
 sub sett {
     my $self = shift;
     my %t=();
+    undef(%Apache::Spreadsheet::sheet_values);
     #
     # Deal with the template row
     foreach my $col ($self->template_cells()) {
@@ -939,8 +893,8 @@ sub sett {
         } elsif  ( $col  =~ /^[A-Z]$/  ) {
             if ($formula !~ /^\!/ && exists($self->{'constants'}->{$cell})
 		&& $self->{'constants'}->{$cell} ne '') {
-                my $data = $self->{'constants'}->{$cell};
-                $t{$cell} = $data;
+		$Apache::Spreadsheet::sheet_values{$cell}=
+		    eval($self->{'constants'}->{$cell});
             }
         } else { # $row > 1 and $col =~ /[a-z]
             $t{$cell}=$formula;
@@ -959,11 +913,11 @@ sub sett {
 sub sync_safe_space {
     my $self = shift;
     # Inside the safe space 'formulas' has a diabolical alter-ego named 'f'.
-    %{$self->{'safe'}->varglob('f')}=%{$self->{'formulas'}};
+    #%{$self->{'safe'}->varglob('f')}=%{$self->{'formulas'}};
     # 'constants' leads a peaceful hidden life of 'c'.
     %{$self->{'safe'}->varglob('c')}=%{$self->{'constants'}};
     # 'othersheets' hides as 'os', a disguise few can penetrate.
-    @{$self->{'safe'}->varglob('os')}=@{$self->{'othersheets'}};
+    #@{$self->{'safe'}->varglob('os')}=@{$self->{'othersheets'}};
 }
 
 ##
@@ -1016,15 +970,32 @@ sub formulas {
         $self->{'formulas'} = $formulas;
         $self->{'rows'} = [];
         $self->{'template_cells'} = [];
+	$self->{'loaded'} = 1;
         return;
     } else {
+	$self->check_formulas_loaded();
         return %{$self->{'formulas'}};
     }
 }
 
+sub check_formulas_loaded {
+    my $self=shift;
+    if (!$self->{'loaded'}) {
+	$self->{'loaded'}=1;
+	# Load in the spreadsheet definition
+	if (exists($env{'form.workcopy'}) && 
+	    $self->{'type'} eq $env{'form.workcopy'}) {
+	    $self->load_tmp();
+	} else {
+	    $self->load();
+	}
+    }
+}
+
 sub set_formula {
     my $self = shift;
     my ($cell,$formula) = @_;
+    $self->check_formulas_loaded();
     $self->{'formulas'}->{$cell}=$formula;
     return;
 }
@@ -1034,7 +1005,7 @@ sub set_formula {
 ##
 sub formulas_keys {
     my $self = shift;
-    my @keys = keys(%{$self->{'formulas'}});
+    $self->check_formulas_loaded();
     return keys(%{$self->{'formulas'}});
 }
 
@@ -1045,6 +1016,7 @@ sub formulas_keys {
 sub formula {
     my $self = shift;
     my $cell = shift;
+    $self->check_formulas_loaded();
     if (defined($cell) && exists($self->{'formulas'}->{$cell})) {
         return $self->{'formulas'}->{$cell};
     }
@@ -1123,6 +1095,7 @@ sub rebuild_stats {
     my $self = shift;
     $self->{'rows'}=[];
     $self->{'template_cells'}=[];
+    $self->check_formulas_loaded();
     while (my ($cell,$formula) = each(%{$self->{'formulas'}})) {
         push(@{$self->{'rows'}},$1) if ($cell =~ /^A(\d+)/ && $1 != 0);
         push(@{$self->{'template_cells'}},$1) if ($cell =~ /^template_(\w+)/);
@@ -1563,6 +1536,7 @@ sub outsheet_xml   {
     ## Will be rendered for the user
     ## But not on this day
     my $Str = '<spreadsheet type="'.$self->{'type'}.'">'."\n";
+    $self->check_formulas_loaded();
     while (my ($cell,$formula) = each(%{$self->{'formulas'}})) {
         if ($cell =~ /^template_(\w+)/) {
             my $col = $1;
@@ -1645,7 +1619,6 @@ sub load {
     my $stype = $self->{'type'};
     my $cnum  = $self->{'cnum'};
     my $cdom  = $self->{'cdom'};
-    my $chome = $self->{'chome'};
     #
     my $filename = $self->filename();
     my $cachekey = join('_',($cnum,$cdom,$stype,$filename));
@@ -1654,7 +1627,11 @@ sub load {
     my ($formulas);
     if (exists($spreadsheets{$cachekey})) {
         $formulas = $spreadsheets{$cachekey}->{'formulas'};
-    } else {
+	$self->formulas($formulas);
+        $self->{'row_source'}=$spreadsheets{$cachekey}->{'row_source'};
+        $self->{'row_numbers'}=$spreadsheets{$cachekey}->{'row_numbers'};
+        $self->{'maxrow'}=$spreadsheets{$cachekey}->{'maxrow'};
+   } else {
         # Not cached, need to read
         if (! defined($filename)) {
             $formulas = $self->load_system_default_sheet();
@@ -1688,16 +1665,28 @@ sub load {
             }
         }
         $filename=$self->filename(); # filename may have changed
-        $cachekey = join('_',($cnum,$cdom,$stype,$filename));
-        %{$spreadsheets{$cachekey}->{'formulas'}} = %{$formulas};
+	$cachekey = join('_',($cnum,$cdom,$stype,$filename));
+	if (ref($formulas) eq 'HASH') {
+	    %{$spreadsheets{$cachekey}->{'formulas'}} = %{$formulas};
+	}
+	$self->formulas($formulas);
+	$self->set_row_sources();
+	$self->set_row_numbers();
+	if (ref($self->{'row_source'})) {
+	    %{$spreadsheets{$cachekey}->{'row_source'}} = 
+		%{$self->{'row_source'}};
+	}
+	if (ref($self->{'row_numbers'})) {
+	    %{$spreadsheets{$cachekey}->{'row_numbers'}} = 
+		%{$self->{'row_numbers'}};
+	}
+        $spreadsheets{$cachekey}->{'maxrow'} = $self->{'maxrow'};
     }
-    $self->formulas($formulas);
-    $self->set_row_sources();
-    $self->set_row_numbers();
 }
 
 sub set_row_sources {
     my $self = shift;
+    $self->check_formulas_loaded();
     while (my ($cell,$value) = each(%{$self->{'formulas'}})) {
         next if ($cell !~ /^A(\d+)/ || $1 < 1);
         my $row = $1;
@@ -1708,6 +1697,7 @@ sub set_row_sources {
 
 sub set_row_numbers {
     my $self = shift;
+    $self->check_formulas_loaded();
     while (my ($cell,$value) = each(%{$self->{'formulas'}})) {
 	next if ($cell !~ /^A(\d+)$/);
         next if (! defined($value));
@@ -1742,7 +1732,6 @@ sub save {
         my $stype = $self->{'type'};
         my $cnum  = $self->{'cnum'};
         my $cdom  = $self->{'cdom'};
-        my $chome = $self->{'chome'};
         my $filename    = $self->{'filename'};
         my $cachekey = join('_',($cnum,$cdom,$stype,$filename));
         # Cache new sheet
@@ -1762,6 +1751,8 @@ sub save {
                                 {'spreadsheet_default_'.$stype => $filename },
                                           $cdom,$cnum);
             return $reply if ($reply ne 'ok');
+	    &Apache::lonnet::appenv('course.'.$self->{'cid'}.'.spreadsheet_default_'.
+				    $self->{'type'} => $self->filename());
         } 
         if ($self->is_default()) {
             if ($self->{'type'} eq 'studentcalc') {