version 1.42, 2005/04/21 17:29:16
|
version 1.50, 2005/05/15 04:55:33
|
Line 106 sub new {
|
Line 106 sub new {
|
cid => $env{'request.course.id'}, |
cid => $env{'request.course.id'}, |
cnum => $env{'course.'.$env{'request.course.id'}.'.num'}, |
cnum => $env{'course.'.$env{'request.course.id'}.'.num'}, |
cdom => $env{'course.'.$env{'request.course.id'}.'.domain'}, |
cdom => $env{'course.'.$env{'request.course.id'}.'.domain'}, |
chome => $env{'course.'.$env{'request.course.id'}.'.home'}, |
|
coursedesc => $env{'course.'.$env{'request.course.id'}.'.description'}, |
coursedesc => $env{'course.'.$env{'request.course.id'}.'.description'}, |
coursefilename => $env{'request.course.fn'}, |
coursefilename => $env{'request.course.fn'}, |
# |
# |
# Flags |
# Flags |
temporary => 0, # true if this sheet has been modified but not saved |
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 |
# blackout is used to determine if any data needs to be hidden from the |
# student. |
# student. |
Line 126 sub new {
|
Line 126 sub new {
|
othersheets => [], |
othersheets => [], |
}; |
}; |
# |
# |
$self->{'uhome'} = &Apache::lonnet::homeserver($name,$domain); |
|
# |
|
bless($self,$class); |
bless($self,$class); |
# |
|
# Load in the spreadsheet definition |
|
$self->filename($filename); |
$self->filename($filename); |
if (exists($env{'form.workcopy'}) && |
# |
$self->{'type'} eq $env{'form.workcopy'}) { |
|
$self->load_tmp(); |
|
} else { |
|
$self->load(); |
|
} |
|
return $self; |
return $self; |
} |
} |
|
|
Line 328 sub initialize_safe_space {
|
Line 319 sub initialize_safe_space {
|
$safeeval->deny(":base_io"); |
$safeeval->deny(":base_io"); |
$safehole->wrap(\&Apache::lonnet::EXT,$safeeval,'&Apache::lonnet::EXT'); |
$safehole->wrap(\&Apache::lonnet::EXT,$safeeval,'&Apache::lonnet::EXT'); |
$safehole->wrap(\&mask,$safeeval,'&mask'); |
$safehole->wrap(\&mask,$safeeval,'&mask'); |
|
$safehole->wrap(\&Apache::lonnet::logthis,$safeeval,'&logthis'); |
$safeeval->share('$@'); |
$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'; |
my $code=<<'ENDDEFS'; |
# ---------------------------------------------------- Inside of the safe space |
# ---------------------------------------------------- Inside of the safe space |
# |
# |
Line 338 sub initialize_safe_space {
|
Line 334 sub initialize_safe_space {
|
# c: preloaded constants (A-column) |
# c: preloaded constants (A-column) |
# rl: row label |
# rl: row label |
# os: other spreadsheets (for student spreadsheet only) |
# os: other spreadsheets (for student spreadsheet only) |
undef %sheet_values; # Holds the (computed, final) values for the sheet |
undef %t; # Holds the forumlas of the spreadsheet to be computed. Set in |
# This is only written to by &calc, the spreadsheet computation routine. |
# &sett, which does the translation of strings like C5 into the value |
# It is read by many functions |
# in C5. Used in &calc - %t holds the values that are actually eval'd. |
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 %f; # Holds the formulas for each cell. This is the users |
undef %f; # Holds the formulas for each cell. This is the users |
# (spreadsheet authors) data for each cell. |
# (spreadsheet authors) data for each cell. |
undef %c; # Holds the constants for a sheet. In the assessment |
undef %c; # Holds the constants for a sheet. In the assessment |
Line 364 $filename = '';
|
Line 357 $filename = '';
|
# |
# |
# user data |
# user data |
$name = ''; |
$name = ''; |
$uhome = ''; |
|
$domain = ''; |
$domain = ''; |
# |
# |
# course data |
# course data |
$csec = ''; |
$csec = ''; |
$chome= ''; |
|
$cnum = ''; |
$cnum = ''; |
$cdom = ''; |
$cdom = ''; |
$cid = ''; |
$cid = ''; |
Line 412 returns the number of items in the range
|
Line 403 returns the number of items in the range
|
|
|
#------------------------------------------------------- |
#------------------------------------------------------- |
sub NUM { |
sub NUM { |
my $mask=&mask(@_); |
my $values=&get_values(@_); |
my $num= $#{@{grep(eval("/$mask/"),keys(%sheet_values))}}+1; |
my $num= scalar(@$values); |
return $num; |
return $num; |
} |
} |
|
|
Line 428 sub NUM {
|
Line 419 sub NUM {
|
#------------------------------------------------------- |
#------------------------------------------------------- |
sub BIN { |
sub BIN { |
my ($low,$high,$lower,$upper)=@_; |
my ($low,$high,$lower,$upper)=@_; |
my $mask=&mask($lower,$upper); |
my $values=&get_values($lower,$upper); |
my $num=0; |
my $num=0; |
foreach (grep eval("/$mask/"),keys(%sheet_values)) { |
foreach (@$values) { |
if (($sheet_values{$_}>=$low) && ($sheet_values{$_}<=$high)) { |
if (($_>=$low) && ($_<=$high)) { |
$num++; |
$num++; |
} |
} |
} |
} |
Line 450 returns the sum of items in the range.
|
Line 441 returns the sum of items in the range.
|
|
|
#------------------------------------------------------- |
#------------------------------------------------------- |
sub SUM { |
sub SUM { |
my $mask=&mask(@_); |
my $values=&get_values(@_); |
my $sum=0; |
my $sum=0; |
foreach (grep eval("/$mask/"),keys(%sheet_values)) { |
foreach (@$values) { |
$sum+=$sheet_values{$_}; |
$sum+=$_; |
} |
} |
return $sum; |
return $sum; |
} |
} |
Line 470 compute the average of the items in the
|
Line 461 compute the average of the items in the
|
|
|
#------------------------------------------------------- |
#------------------------------------------------------- |
sub MEAN { |
sub MEAN { |
my $mask=&mask(@_); |
my $values=&get_values(@_); |
my $sum=0; |
my $sum=0; |
my $num=0; |
my $num=0; |
foreach (grep eval("/$mask/"),keys(%sheet_values)) { |
foreach (@$values) { |
$sum+=$sheet_values{$_}; |
$sum+=$_; |
$num++; |
$num++; |
} |
} |
if ($num) { |
if ($num) { |
Line 496 compute the standard deviation of the it
|
Line 487 compute the standard deviation of the it
|
|
|
#------------------------------------------------------- |
#------------------------------------------------------- |
sub STDDEV { |
sub STDDEV { |
my $mask=&mask(@_); |
my $values=&get_values(@_); |
my $sum=0; my $num=0; |
my $sum=0; my $num=0; |
foreach (grep eval("/$mask/"),keys(%sheet_values)) { |
foreach (@$values) { |
$sum+=$sheet_values{$_}; |
$sum+=$_; |
$num++; |
$num++; |
} |
} |
unless ($num>1) { return undef; } |
unless ($num>1) { return undef; } |
my $mean=$sum/$num; |
my $mean=$sum/$num; |
$sum=0; |
$sum=0; |
foreach (grep eval("/$mask/"),keys(%sheet_values)) { |
foreach (@$values) { |
$sum+=($sheet_values{$_}-$mean)**2; |
$sum+=($_-$mean)**2; |
} |
} |
return sqrt($sum/($num-1)); |
return sqrt($sum/($num-1)); |
} |
} |
Line 523 compute the product of the items in the
|
Line 514 compute the product of the items in the
|
|
|
#------------------------------------------------------- |
#------------------------------------------------------- |
sub PROD { |
sub PROD { |
my $mask=&mask(@_); |
my $values=&get_values(@_); |
my $prod=1; |
my $prod=1; |
foreach (grep eval("/$mask/"),keys(%sheet_values)) { |
foreach (@$values) { |
$prod*=$sheet_values{$_}; |
$prod*=$_; |
} |
} |
return $prod; |
return $prod; |
} |
} |
Line 543 compute the maximum of the items in the
|
Line 534 compute the maximum of the items in the
|
|
|
#------------------------------------------------------- |
#------------------------------------------------------- |
sub MAX { |
sub MAX { |
my $mask=&mask(@_); |
my $values=&get_values(@_); |
my $max='-'; |
my $max='-'; |
foreach (grep eval("/$mask/"),keys(%sheet_values)) { |
foreach (@$values) { |
unless ($max) { $max=$sheet_values{$_}; } |
if (($_>$max) || ($max eq '-')) { |
if (($sheet_values{$_}>$max) || ($max eq '-')) { |
$max=$_; |
$max=$sheet_values{$_}; |
|
} |
} |
} |
} |
return $max; |
return $max; |
Line 566 compute the minimum of the items in the
|
Line 556 compute the minimum of the items in the
|
|
|
#------------------------------------------------------- |
#------------------------------------------------------- |
sub MIN { |
sub MIN { |
my $mask=&mask(@_); |
my $values=&get_values(@_); |
my $min='-'; |
my $min='-'; |
foreach (grep eval("/$mask/"),keys(%sheet_values)) { |
foreach (@$values) { |
unless ($max) { $max=$sheet_values{$_}; } |
if (($_<$min) || ($min eq '-')) { |
if (($sheet_values{$_}<$min) || ($min eq '-')) { |
$min=$_; |
$min=$sheet_values{$_}; |
|
} |
} |
} |
} |
return $min; |
return $min; |
Line 591 compute the sum of the largest 'num' ite
|
Line 580 compute the sum of the largest 'num' ite
|
#------------------------------------------------------- |
#------------------------------------------------------- |
sub SUMMAX { |
sub SUMMAX { |
my ($num,$lower,$upper)=@_; |
my ($num,$lower,$upper)=@_; |
my $mask=&mask($lower,$upper); |
my $values=&get_values($lower,$upper); |
my @inside=(); |
my @inside=sort {$a <=> $b} (@$values); |
foreach (grep eval("/$mask/"),keys(%sheet_values)) { |
|
push (@inside,$sheet_values{$_}); |
|
} |
|
@inside=sort(@inside); |
|
my $sum=0; my $i; |
my $sum=0; my $i; |
for ($i=$#inside;(($i>$#inside-$num) && ($i>=0));$i--) { |
for ($i=$#inside;(($i>$#inside-$num) && ($i>=0));$i--) { |
$sum+=$inside[$i]; |
$sum+=$inside[$i]; |
Line 618 compute the sum of the smallest 'num' it
|
Line 603 compute the sum of the smallest 'num' it
|
#------------------------------------------------------- |
#------------------------------------------------------- |
sub SUMMIN { |
sub SUMMIN { |
my ($num,$lower,$upper)=@_; |
my ($num,$lower,$upper)=@_; |
my $mask=&mask($lower,$upper); |
my $values=&get_values($lower,$upper); |
my @inside=(); |
my @inside=sort {$a <=> $b} (@$values); |
foreach (grep eval("/$mask/"),keys(%sheet_values)) { |
|
$inside[$#inside+1]=$sheet_values{$_}; |
|
} |
|
@inside=sort(@inside); |
|
my $sum=0; my $i; |
my $sum=0; my $i; |
for ($i=0;(($i<$num) && ($i<=$#inside));$i++) { |
for ($i=0;(($i<$num) && ($i<=$#inside));$i++) { |
$sum+=$inside[$i]; |
$sum+=$inside[$i]; |
Line 679 sub MAXPARM {
|
Line 660 sub MAXPARM {
|
return $max; |
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 { |
sub calc { |
%sheet_values = %t; |
|
my $notfinished = 1; |
my $notfinished = 1; |
my $lastcalc = ''; |
my $lastcalc = ''; |
my $depth = 0; |
my $depth = 0; |
Line 722 ENDDEFS
|
Line 759 ENDDEFS
|
# itself |
# itself |
my $initstring = ''; |
my $initstring = ''; |
foreach (qw/name domain type symb cid csec coursefilename |
foreach (qw/name domain type symb cid csec coursefilename |
cnum cdom chome uhome/) { |
cnum cdom/) { |
$initstring.= qq{\$$_="$self->{$_}";}; |
$initstring.= qq{\$$_="$self->{$_}";}; |
} |
} |
$initstring.=qq{\$usection="$usection";}; |
$initstring.=qq{\$usection="$usection";}; |
Line 742 ENDDEFS
|
Line 779 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 |
## sub add_hash_to_safe {} # spreadsheet, would like to destroy |
## |
## |
Line 907 sub expandnamed {
|
Line 858 sub expandnamed {
|
sub sett { |
sub sett { |
my $self = shift; |
my $self = shift; |
my %t=(); |
my %t=(); |
|
undef(%Apache::Spreadsheet::sheet_values); |
# |
# |
# Deal with the template row |
# Deal with the template row |
foreach my $col ($self->template_cells()) { |
foreach my $col ($self->template_cells()) { |
Line 939 sub sett {
|
Line 891 sub sett {
|
} elsif ( $col =~ /^[A-Z]$/ ) { |
} elsif ( $col =~ /^[A-Z]$/ ) { |
if ($formula !~ /^\!/ && exists($self->{'constants'}->{$cell}) |
if ($formula !~ /^\!/ && exists($self->{'constants'}->{$cell}) |
&& $self->{'constants'}->{$cell} ne '') { |
&& $self->{'constants'}->{$cell} ne '') { |
my $data = $self->{'constants'}->{$cell}; |
$Apache::Spreadsheet::sheet_values{$cell}= |
$t{$cell} = $data; |
eval($self->{'constants'}->{$cell}); |
} |
} |
} else { # $row > 1 and $col =~ /[a-z] |
} else { # $row > 1 and $col =~ /[a-z] |
$t{$cell}=$formula; |
$t{$cell}=$formula; |
Line 959 sub sett {
|
Line 911 sub sett {
|
sub sync_safe_space { |
sub sync_safe_space { |
my $self = shift; |
my $self = shift; |
# Inside the safe space 'formulas' has a diabolical alter-ego named 'f'. |
# 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'. |
# 'constants' leads a peaceful hidden life of 'c'. |
%{$self->{'safe'}->varglob('c')}=%{$self->{'constants'}}; |
%{$self->{'safe'}->varglob('c')}=%{$self->{'constants'}}; |
# 'othersheets' hides as 'os', a disguise few can penetrate. |
# 'othersheets' hides as 'os', a disguise few can penetrate. |
@{$self->{'safe'}->varglob('os')}=@{$self->{'othersheets'}}; |
#@{$self->{'safe'}->varglob('os')}=@{$self->{'othersheets'}}; |
} |
} |
|
|
## |
## |
Line 1018 sub formulas {
|
Line 970 sub formulas {
|
$self->{'template_cells'} = []; |
$self->{'template_cells'} = []; |
return; |
return; |
} else { |
} else { |
|
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(); |
|
} |
|
} |
return %{$self->{'formulas'}}; |
return %{$self->{'formulas'}}; |
} |
} |
} |
} |
Line 1645 sub load {
|
Line 1607 sub load {
|
my $stype = $self->{'type'}; |
my $stype = $self->{'type'}; |
my $cnum = $self->{'cnum'}; |
my $cnum = $self->{'cnum'}; |
my $cdom = $self->{'cdom'}; |
my $cdom = $self->{'cdom'}; |
my $chome = $self->{'chome'}; |
|
# |
# |
my $filename = $self->filename(); |
my $filename = $self->filename(); |
my $cachekey = join('_',($cnum,$cdom,$stype,$filename)); |
my $cachekey = join('_',($cnum,$cdom,$stype,$filename)); |
Line 1742 sub save {
|
Line 1703 sub save {
|
my $stype = $self->{'type'}; |
my $stype = $self->{'type'}; |
my $cnum = $self->{'cnum'}; |
my $cnum = $self->{'cnum'}; |
my $cdom = $self->{'cdom'}; |
my $cdom = $self->{'cdom'}; |
my $chome = $self->{'chome'}; |
|
my $filename = $self->{'filename'}; |
my $filename = $self->{'filename'}; |
my $cachekey = join('_',($cnum,$cdom,$stype,$filename)); |
my $cachekey = join('_',($cnum,$cdom,$stype,$filename)); |
# Cache new sheet |
# Cache new sheet |