--- loncom/interface/spreadsheet/Spreadsheet.pm 2003/12/05 22:24:20 1.21.2.1 +++ loncom/interface/spreadsheet/Spreadsheet.pm 2005/02/03 23:59:41 1.38 @@ -1,5 +1,5 @@ # -# $Id: Spreadsheet.pm,v 1.21.2.1 2003/12/05 22:24:20 matthew Exp $ +# $Id: Spreadsheet.pm,v 1.38 2005/02/03 23:59:41 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -48,6 +48,8 @@ Spreadsheet package Apache::Spreadsheet; use strict; +#use warnings FATAL=>'all'; +#no warnings 'uninitialized'; use Apache::Constants qw(:common :http); use Apache::lonnet; use Safe; @@ -57,6 +59,7 @@ use HTML::Entities(); use HTML::TokeParser; use Spreadsheet::WriteExcel; use Time::HiRes; +use Apache::lonlocal; ## ## Package Variables @@ -83,6 +86,12 @@ sub new { my ($stype) = ($class =~ /Apache::(.*)$/); # my ($name,$domain,$filename,$usymb)=@_; + if (! defined($name) || $name eq '') { + $name = $ENV{'user.name'}; + } + if (! defined($domain) || $domain eq '') { + $domain = $ENV{'user.domain'}; + } # my $self = { name => $name, @@ -90,7 +99,7 @@ sub new { type => $stype, symb => $usymb, errorlog => '', - maxrow => '', + maxrow => 0, cid => $ENV{'request.course.id'}, cnum => $ENV{'course.'.$ENV{'request.course.id'}.'.num'}, cdom => $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}, @@ -157,7 +166,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'}; @@ -229,6 +239,16 @@ sub initialize { # the descendents of the spreadsheet class. } +sub clear_package { + # This method is here to remind you that it will be overridden by + # the descendents of the spreadsheet class. +} + +sub cleanup { + my $self = shift(); + $self->clear_package(); +} + sub initialize_spreadsheet_package { &load_spreadsheet_expirationdates(); &clear_spreadsheet_definition_cache(); @@ -248,6 +268,7 @@ sub load_spreadsheet_expirationdates { sub check_expiration_time { my $self = shift; my ($time)=@_; + return 0 if (! defined($time)); my ($key1,$key2,$key3,$key4,$key5); # Description of keys # @@ -286,18 +307,26 @@ Returns the safe space required by a Spr =cut ###################################################### +{ + + my $safeeval; + sub initialize_safe_space { - my $self = shift; - my $safeeval = new Safe(shift); - my $safehole = new Safe::Hole; - $safeeval->permit("entereval"); - $safeeval->permit(":base_math"); - $safeeval->permit("sort"); - $safeeval->deny(":base_io"); - $safehole->wrap(\&Apache::lonnet::EXT,$safeeval,'&EXT'); - $safehole->wrap(\&mask,$safeeval,'&mask'); - $safeeval->share('$@'); - my $code=<<'ENDDEFS'; + 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; + $safeeval->permit("entereval"); + $safeeval->permit(":base_math"); + $safeeval->permit("sort"); + $safeeval->deny(":base_io"); + $safehole->wrap(\&Apache::lonnet::EXT,$safeeval,'&Apache::lonnet::EXT'); + $safehole->wrap(\&mask,$safeeval,'&mask'); + $safeeval->share('$@'); + my $code=<<'ENDDEFS'; # ---------------------------------------------------- Inside of the safe space # # f: formulas @@ -353,6 +382,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. @@ -362,7 +410,7 @@ returns the number of items in the range #------------------------------------------------------- sub NUM { my $mask=&mask(@_); - my $num= $#{@{grep(/$mask/,keys(%sheet_values))}}+1; + my $num= $#{@{grep(eval("/$mask/"),keys(%sheet_values))}}+1; return $num; } @@ -379,7 +427,7 @@ sub BIN { my ($low,$high,$lower,$upper)=@_; my $mask=&mask($lower,$upper); my $num=0; - foreach (grep /$mask/,keys(%sheet_values)) { + foreach (grep eval("/$mask/"),keys(%sheet_values)) { if (($sheet_values{$_}>=$low) && ($sheet_values{$_}<=$high)) { $num++; } @@ -401,7 +449,7 @@ returns the sum of items in the range. sub SUM { my $mask=&mask(@_); my $sum=0; - foreach (grep /$mask/,keys(%sheet_values)) { + foreach (grep eval("/$mask/"),keys(%sheet_values)) { $sum+=$sheet_values{$_}; } return $sum; @@ -422,7 +470,7 @@ sub MEAN { my $mask=&mask(@_); my $sum=0; my $num=0; - foreach (grep /$mask/,keys(%sheet_values)) { + foreach (grep eval("/$mask/"),keys(%sheet_values)) { $sum+=$sheet_values{$_}; $num++; } @@ -447,14 +495,14 @@ compute the standard deviation of the it sub STDDEV { my $mask=&mask(@_); my $sum=0; my $num=0; - foreach (grep /$mask/,keys(%sheet_values)) { + foreach (grep eval("/$mask/"),keys(%sheet_values)) { $sum+=$sheet_values{$_}; $num++; } unless ($num>1) { return undef; } my $mean=$sum/$num; $sum=0; - foreach (grep /$mask/,keys(%sheet_values)) { + foreach (grep eval("/$mask/"),keys(%sheet_values)) { $sum+=($sheet_values{$_}-$mean)**2; } return sqrt($sum/($num-1)); @@ -474,7 +522,7 @@ compute the product of the items in the sub PROD { my $mask=&mask(@_); my $prod=1; - foreach (grep /$mask/,keys(%sheet_values)) { + foreach (grep eval("/$mask/"),keys(%sheet_values)) { $prod*=$sheet_values{$_}; } return $prod; @@ -494,7 +542,7 @@ compute the maximum of the items in the sub MAX { my $mask=&mask(@_); my $max='-'; - foreach (grep /$mask/,keys(%sheet_values)) { + foreach (grep eval("/$mask/"),keys(%sheet_values)) { unless ($max) { $max=$sheet_values{$_}; } if (($sheet_values{$_}>$max) || ($max eq '-')) { $max=$sheet_values{$_}; @@ -517,7 +565,7 @@ compute the minimum of the items in the sub MIN { my $mask=&mask(@_); my $min='-'; - foreach (grep /$mask/,keys(%sheet_values)) { + foreach (grep eval("/$mask/"),keys(%sheet_values)) { unless ($max) { $max=$sheet_values{$_}; } if (($sheet_values{$_}<$min) || ($min eq '-')) { $min=$sheet_values{$_}; @@ -542,7 +590,7 @@ sub SUMMAX { my ($num,$lower,$upper)=@_; my $mask=&mask($lower,$upper); my @inside=(); - foreach (grep /$mask/,keys(%sheet_values)) { + foreach (grep eval("/$mask/"),keys(%sheet_values)) { push (@inside,$sheet_values{$_}); } @inside=sort(@inside); @@ -569,7 +617,7 @@ sub SUMMIN { my ($num,$lower,$upper)=@_; my $mask=&mask($lower,$upper); my @inside=(); - foreach (grep /$mask/,keys(%sheet_values)) { + foreach (grep eval("/$mask/"),keys(%sheet_values)) { $inside[$#inside+1]=$sheet_values{$_}; } @inside=sort(@inside); @@ -595,7 +643,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})) { @@ -620,7 +667,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})) { @@ -664,20 +710,25 @@ sub calc { # ------------------------------------------- End of "Inside of the safe space" ENDDEFS - $safeeval->reval($code); + $safeeval->reval($code); + } $self->{'safe'} = $safeeval; $self->{'root'} = $self->{'safe'}->root(); # # 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; } + +} + ###################################################### =pod @@ -691,6 +742,17 @@ 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 ###################################################### { @@ -705,78 +767,62 @@ sub mask { } $upper = $lower if (! defined($upper)); # - my ($la,$ld) = ($lower=~/([A-Za-z]|\*)(\d+|\*)/); - my ($ua,$ud) = ($upper=~/([A-Za-z]|\*)(\d+|\*)/); + 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-Za-z]'; + $alpha='[A-z]'; } else { - if (($la=~/[A-Z]/) && ($ua=~/[A-Z]/) || - ($la=~/[a-z]/) && ($ua=~/[a-z]/)) { - $alpha='['.$la.'-'.$ua.']'; - } else { - $alpha='['.$la.'-Za-'.$ua.']'; - } - } - if (($ld eq '*') || ($ud eq '*')) { - $num='\d+'; + 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 { - if (length($ld)!=length($ud)) { - $num.='('; - foreach ($ld=~m/\d/g) { - $num.='['.$_.'-9]'; - } - if (length($ud)-length($ld)>1) { - $num.='|\d{'.(length($ld)+1).','.(length($ud)-1).'}'; - } - $num.='|'; - foreach ($ud=~m/\d/g) { - $num.='[0-'.$_.']'; - } - $num.=')'; - } else { - my @lda=($ld=~m/\d/g); - my @uda=($ud=~m/\d/g); - my $i; - my $j=0; - my $notdone=1; - for ($i=0;($i<=$#lda)&&($notdone);$i++) { - if ($lda[$i]==$uda[$i]) { - $num.=$lda[$i]; - $j=$i; - } else { - $notdone=0; - } - } - if ($j<$#lda-1) { - $num.='('.$lda[$j+1]; - for ($i=$j+2;$i<=$#lda;$i++) { - $num.='['.$lda[$i].'-9]'; - } - if ($uda[$j+1]-$lda[$j+1]>1) { - $num.='|['.($lda[$j+1]+1).'-'.($uda[$j+1]-1).']\d{'. - ($#lda-$j-1).'}'; - } - $num.='|'.$uda[$j+1]; - for ($i=$j+2;$i<=$#uda;$i++) { - $num.='[0-'.$uda[$i].']'; - } - $num.=')'; - } else { - if ($lda[-1]!=$uda[-1]) { - $num.='['.$lda[-1].'-'.$uda[-1].']'; - } - } - } + $num = '(\d+)'; } - my $expression ='^'.$alpha.$num."\$"; + 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; +} + } ## @@ -827,7 +873,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); @@ -1127,7 +1172,6 @@ sub calcsheet { return $result; } - sub set_badcalc { my $self = shift(); $self->{'badcalc'} =1; @@ -1165,7 +1209,6 @@ sub calcerror { return; } - ########################################################### ## ## Output Helpers @@ -1174,18 +1217,30 @@ sub calcerror { sub display { my $self = shift; my ($r) = @_; - $self->compute($r); my $outputmode = 'html'; - if ($ENV{'form.output_format'} =~ /^(html|excel|csv)$/) { - $outputmode = $ENV{'form.output_format'}; + foreach ($self->output_options()) { + if ($ENV{'form.output_format'} eq $_->{'value'}) { + $outputmode = $_->{'value'}; + last; + } } if ($outputmode eq 'html') { + $self->compute($r); $self->outsheet_html($r); + } elsif ($outputmode eq 'htmlclasslist') { + # No computation neccessary... This is kludgy + $self->outsheet_htmlclasslist($r); } elsif ($outputmode eq 'excel') { + $self->compute($r); $self->outsheet_excel($r); } 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; } @@ -1197,7 +1252,7 @@ sub html_report_error { my $Str = ''; if ($self->badcalc()) { $Str = '
'.$self->calcerror()."\n"; } @@ -1262,7 +1317,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); # @@ -1285,7 +1340,7 @@ 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.' '; } @@ -1318,12 +1373,27 @@ sub html_header { my $self = shift; return '' if (! $ENV{'request.role.adv'}); return "
Output Format |
---|
'.&output_selector()." |
'.&mt('Output Format').' |
'.$self->output_selector()." |
\n".$csvdata."\n"); return 0; } @@ -1456,13 +1542,12 @@ sub outsheet_csv { } # # Output the body of the spreadsheet - $self->csv_rows($file); + $self->csv_rows($connection,$file); # # Close the csv file close($file); - $r->print($self->html_report_error()); $r->print('
\n\n\n".$Str."\n\n\n"); return $Str; } @@ -1535,8 +1623,7 @@ sub parse_sheet { $formulas{$cell} = $formula; $sources{$cell} = $source if (defined($source)); $parser->get_text('/field'); - } - if ($token->[1] eq 'template') { + } elsif ($token->[1] eq 'template') { $formulas{'template_'.$token->[2]->{'col'}}= $parser->get_text('/template'); } @@ -1590,7 +1677,7 @@ sub load { # Not cached, need to read if (! defined($filename)) { $formulas = $self->load_system_default_sheet(); - } elsif($self->filename() =~ /^\/res\/.*\.spreadsheet$/) { + } elsif($filename =~ /^\/res\/.*\.spreadsheet$/) { # Load a spreadsheet definition file my $sheetxml=&Apache::lonnet::getfile (&Apache::lonnet::filelocation('',$filename)); @@ -1631,7 +1718,7 @@ sub load { sub set_row_sources { my $self = shift; while (my ($cell,$value) = each(%{$self->{'formulas'}})) { - next if ($cell !~ /^A(\d+)/ && $1 > 0); + next if ($cell !~ /^A(\d+)/ || $1 < 1); my $row = $1; $self->{'row_source'}->{$row} = $value; } @@ -1653,6 +1740,9 @@ sub set_row_numbers { ## sub exportrow { my $self = shift; + if (exists($self->{'badcalc'}) && $self->{'badcalc'}) { + return (); + } my @exportarray; foreach my $column (@UC_Columns) { push(@exportarray,$self->value($column.'0')); @@ -1693,8 +1783,10 @@ sub save { return $reply if ($reply ne 'ok'); } if ($self->is_default()) { - &Apache::lonnet::expirespread('','',$self->{'type'},''); - if ($self->{'type'} eq 'assesscalc') { + if ($self->{'type'} eq 'studentcalc') { + &Apache::lonnet::expirespread('','','studentcalc',''); + } elsif ($self->{'type'} eq 'assesscalc') { + &Apache::lonnet::expirespread('','','assesscalc',''); &Apache::lonnet::expirespread('','','studentcalc',''); } } @@ -1786,9 +1878,9 @@ sub othersheets { $self->{'cdom'}, $self->{'cnum'}); my ($tmp) = keys(%results); if ($tmp =~ /^(con_lost|error|no_such_host)/i ) { - @alternatives = ('Default'); + @alternatives = (&mt('Default')); } else { - @alternatives = ('Default', sort (keys(%results))); + @alternatives = (&mt('Default'), sort (keys(%results))); } return @alternatives; }