--- loncom/interface/spreadsheet/Spreadsheet.pm 2003/12/08 19:43:03 1.32 +++ loncom/interface/spreadsheet/Spreadsheet.pm 2005/05/13 21:16:09 1.47 @@ -1,5 +1,5 @@ # -# $Id: Spreadsheet.pm,v 1.32 2003/12/08 19:43:03 matthew Exp $ +# $Id: Spreadsheet.pm,v 1.47 2005/05/13 21:16:09 albertel Exp $ # # Copyright Michigan State University Board of Trustees # @@ -86,6 +86,15 @@ sub new { my ($stype) = ($class =~ /Apache::(.*)$/); # my ($name,$domain,$filename,$usymb)=@_; + if (defined($usymb) && ref($usymb)) { + $usymb = $usymb->symb; + } + if (! defined($name) || $name eq '') { + $name = $env{'user.name'}; + } + if (! defined($domain) || $domain eq '') { + $domain = $env{'user.domain'}; + } # my $self = { name => $name, @@ -94,12 +103,12 @@ sub new { symb => $usymb, errorlog => '', maxrow => 0, - 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'}, + 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 @@ -123,8 +132,8 @@ sub new { # # Load in the spreadsheet definition $self->filename($filename); - if (exists($ENV{'form.workcopy'}) && - $self->{'type'} eq $ENV{'form.workcopy'}) { + if (exists($env{'form.workcopy'}) && + $self->{'type'} eq $env{'form.workcopy'}) { $self->load_tmp(); } else { $self->load(); @@ -151,8 +160,8 @@ sub filename { $newfilename !~ /\w/ || $newfilename eq '') { my $key = 'course.'.$self->{'cid'}.'.spreadsheet_default_'. $self->{'type'}; - if (exists($ENV{$key}) && $ENV{$key} ne '') { - $newfilename = $ENV{$key}; + if (exists($env{$key}) && $env{$key} ne '') { + $newfilename = $env{$key}; } else { $newfilename = 'default_'.$self->{'type'}; } @@ -160,7 +169,8 @@ sub filename { if ($newfilename !~ /\w/ || $newfilename =~ /^\W*$/) { $newfilename = 'default_'.$self->{'type'}; } - if ($newfilename !~ /^default\.$self->{'type'}$/ ) { + if ($newfilename !~ /^default\.$self->{'type'}$/ && + $newfilename !~ /^\/res\/(.*)spreadsheet$/) { if ($newfilename !~ /_$self->{'type'}$/) { $newfilename =~ s/[\s_]*$//; $newfilename .= '_'.$self->{'type'}; @@ -249,10 +259,10 @@ sub initialize_spreadsheet_package { sub load_spreadsheet_expirationdates { undef %expiredates; - my $cid=$ENV{'request.course.id'}; + my $cid=$env{'request.course.id'}; my @tmp = &Apache::lonnet::dump('nohist_expirationdates', - $ENV{'course.'.$cid.'.domain'}, - $ENV{'course.'.$cid.'.num'}); + $env{'course.'.$cid.'.domain'}, + $env{'course.'.$cid.'.num'}); if (lc($tmp[0]) !~ /^error/){ %expiredates = @tmp; } @@ -306,6 +316,9 @@ Returns the safe space required by a Spr sub initialize_safe_space { my $self = shift; + my $usection = &Apache::lonnet::getsection($self->{'domain'}, + $self->{'name'}, + $env{'request.course.id'}); if (! defined($safeeval)) { $safeeval = new Safe(shift); my $safehole = new Safe::Hole; @@ -313,9 +326,14 @@ sub initialize_safe_space { $safeeval->permit(":base_math"); $safeeval->permit("sort"); $safeeval->deny(":base_io"); - $safehole->wrap(\&Apache::lonnet::EXT,$safeeval,'&EXT'); + $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 # @@ -325,12 +343,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 @@ -372,6 +387,25 @@ $errormsg = ''; =pod +=item EXT(parameter) + +Calls the system EXT function to determine the value of the given parameter. + +=cut + +#------------------------------------------------------- +sub EXT { + my ($parameter) = @_; + return '' if (! defined($parameter) || $parameter eq ''); + $parameter =~ s/^parameter\./resource\./; + my $value = &Apache::lonnet::EXT($parameter,$symb,$domain,$name,$usection); + return $value; +} + +#------------------------------------------------------- + +=pod + =item NUM(range) returns the number of items in the range. @@ -380,8 +414,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; } @@ -396,10 +430,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++; } } @@ -418,10 +452,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; } @@ -438,11 +472,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) { @@ -464,17 +498,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)); } @@ -491,10 +525,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; } @@ -511,12 +545,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; @@ -534,12 +567,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; @@ -559,12 +591,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]; @@ -586,12 +614,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]; @@ -614,7 +638,6 @@ parametername should be a string such as sub MINPARM { my ($expression) = @_; my $min = undef; - study($expression); foreach $parameter (keys(%c)) { next if ($parameter !~ /$expression/); if ((! defined($min)) || ($min > $c{$parameter})) { @@ -639,7 +662,6 @@ parametername should be a string such as sub MAXPARM { my ($expression) = @_; my $max = undef; - study($expression); foreach $parameter (keys(%c)) { next if ($parameter !~ /$expression/); if ((! defined($min)) || ($max < $c{$parameter})) { @@ -649,9 +671,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; @@ -691,10 +769,11 @@ ENDDEFS # Place some of the %$self items into the safe space except the safe space # itself my $initstring = ''; - foreach (qw/name domain type usymb cid csec coursefilename + foreach (qw/name domain type symb cid csec coursefilename cnum cdom chome uhome/) { $initstring.= qq{\$$_="$self->{$_}";}; } + $initstring.=qq{\$usection="$usection";}; $self->{'safe'}->reval($initstring); return $self; } @@ -711,92 +790,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 ## @@ -845,7 +838,6 @@ sub expandnamed { my @matches = (); my @values = (); $#matches = -1; - study $expression; while (my($parameter,$value) = each(%{$self->{'constants'}})) { next if ($parameter !~ /$expression/); push(@matches,$parameter); @@ -877,6 +869,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()) { @@ -907,9 +900,10 @@ sub sett { $t{$cell}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g; $t{$cell}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.$self->expandnamed($2)/ge; } elsif ( $col =~ /^[A-Z]$/ ) { - if ($formula !~ /^\!/ && exists($self->{'constants'}->{$cell})) { - my $data = $self->{'constants'}->{$cell}; - $t{$cell} = $data; + if ($formula !~ /^\!/ && exists($self->{'constants'}->{$cell}) + && $self->{'constants'}->{$cell} ne '') { + $Apache::Spreadsheet::sheet_values{$cell}= + eval($self->{'constants'}->{$cell}); } } else { # $row > 1 and $col =~ /[a-z] $t{$cell}=$formula; @@ -928,11 +922,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'}}; } ## @@ -1192,7 +1186,7 @@ sub display { my ($r) = @_; my $outputmode = 'html'; foreach ($self->output_options()) { - if ($ENV{'form.output_format'} eq $_->{'value'}) { + if ($env{'form.output_format'} eq $_->{'value'}) { $outputmode = $_->{'value'}; last; } @@ -1209,6 +1203,9 @@ sub display { } elsif ($outputmode eq 'csv') { $self->compute($r); $self->outsheet_csv($r); + } elsif ($outputmode eq 'xml') { +# $self->compute($r); + $self->outsheet_xml($r); } $self->cleanup(); return; @@ -1233,7 +1230,7 @@ sub html_export_row { my $self = shift(); my ($color) = @_; $color = '#CCCCFF' if (! defined($color)); - my $allowed = &Apache::lonnet::allowed('mgr',$ENV{'request.course.id'}); + my $allowed = &Apache::lonnet::allowed('mgr',$env{'request.course.id'}); my $row_html; my @rowdata = $self->get_row(0); foreach my $cell (@rowdata) { @@ -1250,7 +1247,7 @@ sub html_export_row { sub html_template_row { my $self = shift(); - my $allowed = &Apache::lonnet::allowed('mgr',$ENV{'request.course.id'}); + my $allowed = &Apache::lonnet::allowed('mgr',$env{'request.course.id'}); my ($num_uneditable,$importcolor) = @_; my $row_html; my @rowdata = $self->get_template_row(); @@ -1287,7 +1284,7 @@ sub html_editable_cell { } elsif ($value =~ /^\s*$/ ) { $value = '#'; } else { - $value = &HTML::Entities::encode($value) if ($value !~/ /); + $value = &HTML::Entities::encode($value,'<>&"') if ($value !~/ /); } return $value if (! $allowed); # @@ -1310,14 +1307,14 @@ sub html_editable_cell { sub html_uneditable_cell { my ($cell,$bgcolor) = @_; my $value = (defined($cell) ? $cell->{'value'} : ''); - $value = &HTML::Entities::encode($value) if ($value !~/ /); + $value = &HTML::Entities::encode($value,'<>&"') if ($value !~/ /); return ' '.$value.' '; } sub html_row { my $self = shift(); my ($num_uneditable,$row,$exportcolor,$importcolor) = @_; - my $allowed = &Apache::lonnet::allowed('mgr',$ENV{'request.course.id'}); + my $allowed = &Apache::lonnet::allowed('mgr',$env{'request.course.id'}); my @rowdata = $self->get_row($row); my $num_cols_output = 0; my $row_html; @@ -1341,7 +1338,7 @@ sub html_row { sub html_header { my $self = shift; - return '' if (! $ENV{'request.role.adv'}); + return '' if (! $env{'request.role.adv'}); return "\n". ''."\n". '\n". @@ -1356,6 +1353,8 @@ sub output_options { description => 'HTML'}, {value => 'excel', description => 'Excel'}, +# {value => 'xml', +# description => 'XML'}, {value => 'csv', description => 'Comma Separated Values'},); } @@ -1364,10 +1363,10 @@ sub output_selector { my $self = shift(); my $output_selector = '
'.&mt('Output Format').'
'.$self->output_selector()."