--- loncom/interface/Attic/lonspreadsheet.pm 2002/12/05 15:31:05 1.155 +++ loncom/interface/Attic/lonspreadsheet.pm 2003/01/30 18:37:49 1.166 @@ -1,5 +1,5 @@ # -# $Id: lonspreadsheet.pm,v 1.155 2002/12/05 15:31:05 matthew Exp $ +# $Id: lonspreadsheet.pm,v 1.166 2003/01/30 18:37:49 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -50,12 +50,434 @@ built-in functions. =cut + package Apache::lonspreadsheet; use strict; use Apache::Constants qw(:common :http); use Apache::lonnet; use Apache::lonhtmlcommon; +use HTML::Entities(); + +# --------------------------------------------------------- Various form fields + +sub textfield { + my ($title,$name,$value)=@_; + return "\n<p><b>$title:</b><br>". + '<input type=text name="'.$name.'" size=80 value="'.$value.'">'; +} + +sub hiddenfield { + my ($name,$value)=@_; + return "\n".'<input type=hidden name="'.$name.'" value="'.$value.'">'; +} + +sub selectbox { + my ($title,$name,$value,%options)=@_; + my $selout="\n<p><b>$title:</b><br>".'<select name="'.$name.'">'; + foreach (sort keys(%options)) { + $selout.='<option value="'.$_.'"'; + if ($_ eq $value) { $selout.=' selected'; } + $selout.='>'.$options{$_}.'</option>'; + } + return $selout.'</select>'; +} + +my %oldsheets; +my %loadedcaches; + +# ================================================================ Main handler +# +# Interactive call to screen +# +# +sub handler { + my $r=shift; + + my ($sheettype) = ($r->uri=~/\/(\w+)$/); + + if (! exists($ENV{'form.Status'})) { + $ENV{'form.Status'} = 'Active'; + } + if ( ! exists($ENV{'form.output'}) || + ($sheettype ne 'classcalc' && + lc($ENV{'form.output'}) eq 'recursive excel')) { + $ENV{'form.output'} = 'HTML'; + } + # + # Overload checking + # + # Check this server + my $loaderror=&Apache::lonnet::overloaderror($r); + if ($loaderror) { return $loaderror; } + # Check the course homeserver + $loaderror= &Apache::lonnet::overloaderror($r, + $ENV{'course.'.$ENV{'request.course.id'}.'.home'}); + if ($loaderror) { return $loaderror; } + # + # HTML Header + # + if ($r->header_only) { + $r->content_type('text/html'); + $r->send_http_header; + return OK; + } + # + # Roles Checking + # + # Needs to be in a course + if (! $ENV{'request.course.fn'}) { + # Not in a course, or not allowed to modify parms + $ENV{'user.error.msg'}= + $r->uri.":opa:0:0:Cannot modify spreadsheet"; + return HTTP_NOT_ACCEPTABLE; + } + # + # Get query string for limited number of parameters + # + &Apache::loncommon::get_unprocessed_cgi + ($ENV{'QUERY_STRING'},['uname','udom','usymb','ufn','mapid','resid']); + # + # Deal with restricted student permissions + # + if ($ENV{'request.role'} =~ /^st\./) { + delete $ENV{'form.unewfield'} if (exists($ENV{'form.unewfield'})); + delete $ENV{'form.unewformula'} if (exists($ENV{'form.unewformula'})); + } + # + # Look for special assessment spreadsheets - '_feedback', etc. + # + if (($ENV{'form.usymb'}=~/^\_(\w+)/) && (!$ENV{'form.ufn'} || + $ENV{'form.ufn'} eq '' || + $ENV{'form.ufn'} eq 'default')) { + $ENV{'form.ufn'}='default_'.$1; + } + if (!$ENV{'form.ufn'} || $ENV{'form.ufn'} eq 'default') { + $ENV{'form.ufn'}='course_default_'.$sheettype; + } + # + # Interactive loading of specific sheet? + # + if (($ENV{'form.load'}) && ($ENV{'form.loadthissheet'} ne 'Default')) { + $ENV{'form.ufn'}=$ENV{'form.loadthissheet'}; + } + # + # Determine the user name and domain for the sheet. + my $aname; + my $adom; + unless ($ENV{'form.uname'}) { + $aname=$ENV{'user.name'}; + $adom=$ENV{'user.domain'}; + } else { + $aname=$ENV{'form.uname'}; + $adom=$ENV{'form.udom'}; + } + # + # Open page, try to prevent browser cache. + # + $r->content_type('text/html'); + $r->header_out('Cache-control','no-cache'); + $r->header_out('Pragma','no-cache'); + $r->send_http_header; + # + # Header.... + # + $r->print('<html><head><title>LON-CAPA Spreadsheet</title>'); + my $nothing = "''"; + if ($ENV{'browser.type'} eq 'explorer') { + $nothing = "'javascript:void(0);'"; + } + + if ($ENV{'request.role'} !~ /^st\./) { + $r->print(<<ENDSCRIPT); +<script language="JavaScript"> + + var editwin; + + function celledit(cellname,cellformula) { + var edit_text = ''; + // cellformula may contain less-than and greater-than symbols, so + // we need to escape them? + edit_text +='<html><head><title>Cell Edit Window</title></head><body>'; + edit_text += '<form name="editwinform">'; + edit_text += '<center><h3>Cell '+cellname+'</h3>'; + edit_text += '<textarea name="newformula" cols="40" rows="6"'; + edit_text += ' wrap="off" >'+cellformula+'</textarea>'; + edit_text += '</br>'; + edit_text += '<input type="button" name="accept" value="Accept"'; + edit_text += ' onClick=\\\'javascript:'; + edit_text += 'opener.document.sheet.unewfield.value='; + edit_text += '"'+cellname+'";'; + edit_text += 'opener.document.sheet.unewformula.value='; + edit_text += 'document.editwinform.newformula.value;'; + edit_text += 'opener.document.sheet.submit();'; + edit_text += 'self.close()\\\' />'; + edit_text += ' '; + edit_text += '<input type="button" name="abort" '; + edit_text += 'value="Discard Changes"'; + edit_text += ' onClick="javascript:self.close()" />'; + edit_text += '</center></body></html>'; + + if (editwin != null && !(editwin.closed) ) { + editwin.close(); + } + + editwin = window.open($nothing,'CellEditWin','height=200,width=350,scrollbars=no,resizeable=yes,alwaysRaised=yes,dependent=yes',true); + editwin.document.write(edit_text); + } + + function changesheet(cn) { + document.sheet.unewfield.value=cn; + document.sheet.unewformula.value='changesheet'; + document.sheet.submit(); + } + + function insertrow(cn) { + document.sheet.unewfield.value='insertrow'; + document.sheet.unewformula.value=cn; + document.sheet.submit(); + } + +</script> +ENDSCRIPT + } + $r->print('</head>'.&Apache::loncommon::bodytag('Grades Spreadsheet'). + '<form action="'.$r->uri.'" name="sheet" method="post">'); + $r->print(&hiddenfield('uname',$ENV{'form.uname'}). + &hiddenfield('udom',$ENV{'form.udom'}). + &hiddenfield('usymb',$ENV{'form.usymb'}). + &hiddenfield('unewfield',''). + &hiddenfield('unewformula','')); + $r->rflush(); + # + # Full recalc? + # + # Read new sheet or modified worksheet + my $sheet=Apache::lonspreadsheet::Spreadsheet->new($aname,$adom,$sheettype,$ENV{'form.usymb'}); + if ($ENV{'form.forcerecalc'}) { + $r->print('<h4>Completely Recalculating Sheet ...</h4>'); + $sheet->complete_recalc(); + } + # + # Global directory configs + # + $sheet->includedir($r->dir_config('lonIncludes')); + $sheet->tmpdir($r->dir_config('lonDaemons').'/tmp/'); + # + # Check user permissions + if (($sheet->{'type'} eq 'classcalc' ) || + ($sheet->{'uname'} ne $ENV{'user.name'} ) || + ($sheet->{'udom'} ne $ENV{'user.domain'})) { + unless (&Apache::lonnet::allowed('vgr',$sheet->{'cid'})) { + $r->print('<h1>Access Permission Denied</h1>'. + '</form></body></html>'); + return OK; + } + } + # Print out user information + $r->print('<h2>'.$sheet->{'coursedesc'}.'</h2>'); + if ($sheet->{'type'} ne 'classcalc') { + $r->print('<h2>'.$sheet->gettitle().'</h2><p>'); + } + if ($sheet->{'type'} eq 'assesscalc') { + $r->print('<b>User:</b> '.$sheet->{'uname'}. + '<br /><b>Domain:</b> '.$sheet->{'udom'}.'<br />'); + } + if ($sheet->{'type'} eq 'studentcalc' || + $sheet->{'type'} eq 'assesscalc') { + $r->print('<b>Section/Group:</b>'.$sheet->{'csec'}.'</p>'); + } + # + # If a new formula had been entered, go from work copy + if ($ENV{'form.unewfield'}) { + $r->print('<h2>Modified Workcopy</h2>'); + #$ENV{'form.unewformula'}=~s/\'/\"/g; + $r->print('<p>Cell '.$ENV{'form.unewfield'}.' = <pre>'); + $r->print(&HTML::Entities::encode($ENV{'form.unewformula'}). + '</pre></p>'); + $sheet->{'filename'} = $ENV{'form.ufn'}; + $sheet->tmpread($ENV{'form.unewfield'},$ENV{'form.unewformula'}); + } elsif ($ENV{'form.saveas'}) { + $sheet->{'filename'} = $ENV{'form.ufn'}; + $sheet->tmpread(); + } else { + $sheet->readsheet($ENV{'form.ufn'}); + } + # Additional options + if ($sheet->{'type'} eq 'assesscalc') { + $r->print('<p><font size=+2>'. + '<a href="/adm/studentcalc?'. + 'uname='.$sheet->{'uname'}. + '&udom='.$sheet->{'udom'}.'">'. + 'Level up: Student Sheet</a></font></p>'); + } + if (($sheet->{'type'} eq 'studentcalc') && + (&Apache::lonnet::allowed('vgr',$sheet->{'cid'}))) { + $r->print ('<p><font size=+2><a href="/adm/classcalc">'. + 'Level up: Course Sheet</a></font></p>'); + } + # Recalc button + $r->print('<br />'. + '<input type="submit" name="forcerecalc" '. + 'value="Completely Recalculate Sheet"></p>'); + # Save dialog + if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) { + my $fname=$ENV{'form.ufn'}; + $fname=~s/\_[^\_]+$//; + if ($fname eq 'default') { $fname='course_default'; } + $r->print('<input type=submit name=saveas value="Save as ...">'. + '<input type=text size=20 name=newfn value="'.$fname.'">'. + 'make default: <input type=checkbox name="makedefufn"><p>'); + } + $r->print(&hiddenfield('ufn',$sheet->{'filename'})); + # Load dialog + if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) { + $r->print('<p><input type=submit name=load value="Load ...">'. + '<select name="loadthissheet">'. + '<option name="default">Default</option>'); + foreach ($sheet->othersheets()) { + $r->print('<option name="'.$_.'"'); + if ($ENV{'form.ufn'} eq $_) { + $r->print(' selected'); + } + $r->print('>'.$_.'</option>'); + } + $r->print('</select><p>'); + if ($sheet->{'type'} eq 'studentcalc') { + $sheet->setothersheets($sheet->othersheets('assesscalc')); + } + } + # + # Set up caching mechanisms + # + &Apache::lonspreadsheet::Spreadsheet::load_spreadsheet_expirationdates(); + # Clear out old caches if we have not seen this class before. + if (exists($oldsheets{'course'}) && + $oldsheets{'course'} ne $sheet->{'cid'}) { + undef %oldsheets; + undef %loadedcaches; + } + $oldsheets{'course'} = $sheet->{'cid'}; + # + if ($sheet->{'type'} eq 'classcalc') { + $r->print("Loading previously calculated student sheets ...\n"); + $r->rflush(); + &Apache::lonspreadsheet::Spreadsheet::cachedcsheets(); + } elsif ($sheet->{'type'} eq 'studentcalc') { + $r->print("Loading previously calculated assessment sheets ...\n"); + $r->rflush(); + $sheet->cachedssheets(); + } + # Update sheet, load rows + $r->print("Loaded sheet(s), updating rows ...<br>\n"); + $r->rflush(); + # + $sheet->updatesheet(); + $r->print("Updated rows, loading row data ...\n"); + $r->rflush(); + # + $sheet->loadrows($r); + $r->print("Loaded row data, calculating sheet ...<br>\n"); + $r->rflush(); + # + my $calcoutput=$sheet->calcsheet(); + $r->print('<h3><font color=red>'.$calcoutput.'</h3></font>'); + # See if something to save + if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) { + my $fname=''; + if ($ENV{'form.saveas'} && ($fname=$ENV{'form.newfn'})) { + $fname=~s/\W/\_/g; + if ($fname eq 'default') { $fname='course_default'; } + $fname.='_'.$sheet->{'type'}; + $sheet->{'filename'} = $fname; + $ENV{'form.ufn'}=$fname; + $r->print('<p>Saving spreadsheet: '. + $sheet->writesheet($ENV{'form.makedefufn'}). + '<p>'); + } + } + # + # Write the modified worksheet + $r->print('<b>Current sheet:</b> '.$sheet->{'filename'}.'</p>'); + $sheet->tmpwrite(); + if ($sheet->{'type'} eq 'assesscalc') { + $r->print('<p>Show rows with empty A column: '); + } else { + $r->print('<p>Show empty rows: '); + } + # + $r->print(&hiddenfield('userselhidden','true'). + '<input type="checkbox" name="showall" onClick="submit()"'); + # + if ($ENV{'form.showall'}) { + $r->print(' checked'); + } else { + unless ($ENV{'form.userselhidden'}) { + unless + ($ENV{'course.'.$sheet->{'cid'}.'.hideemptyrows'} eq 'yes') { + $r->print(' checked'); + $ENV{'form.showall'}=1; + } + } + } + $r->print('>'); + # + # output format select box + $r->print(' Output as <select name="output" size="1" onChange="submit()">'. + "\n"); + foreach my $mode (qw/HTML CSV Excel/) { + $r->print('<option value="'.$mode.'"'); + if ($ENV{'form.output'} eq $mode) { + $r->print(' selected '); + } + $r->print('>'.$mode.'</option>'."\n"); + } +# +# Mulit-sheet excel takes too long and does not work at all for large +# classes. Future inclusion of this option may be possible with the +# Spreadsheet::WriteExcel::Big and speed improvements. +# +# if ($sheet->{'type'} eq 'classcalc') { +# $r->print('<option value="recursive excel"'); +# if ($ENV{'form.output'} eq 'recursive excel') { +# $r->print(' selected '); +# } +# $r->print(">Multi-Sheet Excel</option>\n"); +# } + $r->print("</select>\n"); + # + if ($sheet->{'type'} eq 'classcalc') { + $r->print(' Student Status: '. + &Apache::lonhtmlcommon::StatusOptions + ($ENV{'form.Status'},'sheet')); + } + # + # Buttons to insert rows +# $r->print(<<ENDINSERTBUTTONS); +#<br> +#<input type='button' onClick='insertrow("top");' +#value='Insert Row Top'> +#<input type='button' onClick='insertrow("bottom");' +#value='Insert Row Bottom'><br> +#ENDINSERTBUTTONS + # Print out sheet + $sheet->outsheet($r); + $r->print('</form></body></html>'); + # Done + return OK; +} + +1; + +############################################################# +############################################################# +############################################################# + +package Apache::lonspreadsheet::Spreadsheet; + +use strict; +use Apache::Constants qw(:common :http); +use Apache::lonnet; use Apache::loncoursedata; use Apache::File(); use Safe; @@ -65,6 +487,15 @@ use GDBM_File; use HTML::Entities(); use HTML::TokeParser; use Spreadsheet::WriteExcel; +use Time::HiRes; + +# +# These global hashes are dependent on user, course and resource, +# and need to be initialized every time when a sheet is calculated +# +my %courseopt; +my %useropt; +my %parmhash; # # Caches for coursewide information @@ -74,58 +505,299 @@ my %Section; # # Caches for previously calculated spreadsheets # - -my %oldsheets; -my %loadedcaches; my %expiredates; # # Cache for stores of an individual user # - my $cachedassess; my %cachedstores; # -# These cache hashes need to be independent of user, resource and course -# (user and course can/should be in the keys) +# Some hashes for stats on timing and performance # +my %starttimes; +my %usedtimes; +my %numbertimes; + +# +# Directories +# +my $includedir; +my $tmpdir; + +sub includedir { + my $self = shift; + $includedir = shift; +} + +sub tmpdir { + my $self = shift; + $tmpdir = shift; +} my %spreadsheets; +my %loadedcaches; my %courserdatas; my %userrdatas; my %defaultsheets; my %rowlabel_cache; +my %oldsheets; + +sub complete_recalc { + my $self = shift; + undef %spreadsheets; + undef %courserdatas; + undef %userrdatas; + undef %defaultsheets; + undef %rowlabel_cache; +} + +sub get_sheet { + my $self = shift; + my $sheet_id = shift; + my $formulas; + # if we already have the file loaded and parsed, return the formulas + if (exists($self->{'sheets'}->{$sheet_id})) { + $formulas = $self->{'sheets'}->{$sheet_id}; + $self->debug('retrieved '.$sheet_id); + } else { + # load the file + # set $error and return undef if there is an error loading + # parse it + # set $error and return undef if there is an error parsing + } + return $formulas; +} # -# These global hashes are dependent on user, course and resource, -# and need to be initialized every time when a sheet is calculated +# Load previously cached student spreadsheets for this course # -my %courseopt; -my %useropt; -my %parmhash; +sub load_spreadsheet_expirationdates { + undef %expiredates; + my $cid=$ENV{'request.course.id'}; + my @tmp = &Apache::lonnet::dump('nohist_expirationdates', + $ENV{'course.'.$cid.'.domain'}, + $ENV{'course.'.$cid.'.num'}); + if (lc($tmp[0]) !~ /^error/){ + %expiredates = @tmp; + } +} +# ===================================================== Calculated sheets cache # -# Some hashes for stats on timing and performance +# Load previously cached student spreadsheets for this course # +sub cachedcsheets { + my $cid=$ENV{'request.course.id'}; + my @tmp = &Apache::lonnet::dump('nohist_calculatedsheets', + $ENV{'course.'.$cid.'.domain'}, + $ENV{'course.'.$cid.'.num'}); + if ($tmp[0] !~ /^error/) { + my %StupidTempHash = @tmp; + while (my ($key,$value) = each %StupidTempHash) { + $Apache::lonspreadsheet::oldsheets{$key} = $value; + } + } +} -my %starttimes; -my %usedtimes; -my %numbertimes; +# +# Load previously cached assessment spreadsheets for this student +# +sub cachedssheets { + my $self = shift; + my ($uname,$udom) = @_; + $uname = $uname || $self->{'uname'}; + $udom = $udom || $self->{'udom'}; + if (! $Apache::lonspreadsheet::loadedcaches{$uname.'_'.$udom}) { + my @tmp = &Apache::lonnet::dump('nohist_calculatedsheets_'. + $ENV{'request.course.id'}, + $self->{'udom'}, + $self->{'uname'}); + if ($tmp[0] !~ /^error/) { + my %TempHash = @tmp; + my $count = 0; + while (my ($key,$value) = each %TempHash) { + $Apache::lonspreadsheet::oldsheets{$key} = $value; + $count++; + } + $Apache::lonspreadsheet::loadedcaches{$self->{'uname'}.'_'.$self->{'udom'}}=1; + } + } +} -# Stuff that only the screen handler can know +# ======================================================= Forced recalculation? +sub checkthis { + my ($keyname,$time)=@_; + if (! exists($expiredates{$keyname})) { + return 0; + } else { + return ($time<$expiredates{$keyname}); + } +} -my $includedir; -my $tmpdir; +sub forcedrecalc { + my ($uname,$udom,$stype,$usymb)=@_; + my $key=$uname.':'.$udom.':'.$stype.':'.$usymb; + my $time=$Apache::lonspreadsheet::oldsheets{$key.'.time'}; + if ($ENV{'form.forcerecalc'}) { return 1; } + unless ($time) { return 1; } + if ($stype eq 'assesscalc') { + my $map=(split(/___/,$usymb))[0]; + if (&checkthis('::assesscalc:',$time) || + &checkthis('::assesscalc:'.$map,$time) || + &checkthis('::assesscalc:'.$usymb,$time) || + &checkthis($uname.':'.$udom.':assesscalc:',$time) || + &checkthis($uname.':'.$udom.':assesscalc:'.$map,$time) || + &checkthis($uname.':'.$udom.':assesscalc:'.$usymb,$time)) { + return 1; + } + } else { + if (&checkthis('::studentcalc:',$time) || + &checkthis($uname.':'.$udom.':studentcalc:',$time)) { + return 1; + } + } + return 0; +} + + +################################################## +################################################## + +=pod + +=item &parmval() + +Determine the value of a parameter. + +Inputs: $what, the parameter needed, $symb, $uname, $udom, $csec + +Returns: The value of a parameter, or '' if none. + +This function cascades through the possible levels searching for a value for +a parameter. The levels are checked in the following order: +user, course (at section level and course level), map, and lonnet::metadata. +This function uses %parmhash, which must be tied prior to calling it. +This function also requires %courseopt and %useropt to be initialized for +this user and course. + +=cut + +################################################## +################################################## +sub parmval { + my ($what,$symb,$uname,$udom,$csec)=@_; + return '' if (!$symb); + # + my $cid = $ENV{'request.course.id'}; + my $result=''; + # + my ($mapname,$id,$fn)=split(/\_\_\_/,$symb); + # Cascading lookup scheme + my $rwhat=$what; + $what =~ s/^parameter\_//; + $what =~ s/\_([^\_]+)$/\.$1/; + # + my $symbparm = $symb.'.'.$what; + my $mapparm = $mapname.'___(all).'.$what; + my $usercourseprefix = $uname.'_'.$udom.'_'.$cid; + # + my $seclevel = $usercourseprefix.'.['.$csec.'].'.$what; + my $seclevelr = $usercourseprefix.'.['.$csec.'].'.$symbparm; + my $seclevelm = $usercourseprefix.'.['.$csec.'].'.$mapparm; + # + my $courselevel = $usercourseprefix.'.'.$what; + my $courselevelr = $usercourseprefix.'.'.$symbparm; + my $courselevelm = $usercourseprefix.'.'.$mapparm; + # fourth, check user + if (defined($uname)) { + return $useropt{$courselevelr} if (defined($useropt{$courselevelr})); + return $useropt{$courselevelm} if (defined($useropt{$courselevelm})); + return $useropt{$courselevel} if (defined($useropt{$courselevel})); + } + # third, check course + if (defined($csec)) { + return $courseopt{$seclevelr} if (defined($courseopt{$seclevelr})); + return $courseopt{$seclevelm} if (defined($courseopt{$seclevelm})); + return $courseopt{$seclevel} if (defined($courseopt{$seclevel})); + } + # + return $courseopt{$courselevelr} if (defined($courseopt{$courselevelr})); + return $courseopt{$courselevelm} if (defined($courseopt{$courselevelm})); + return $courseopt{$courselevel} if (defined($courseopt{$courselevel})); + # second, check map parms + my $thisparm = $parmhash{$symbparm}; + return $thisparm if (defined($thisparm)); + # first, check default + return &Apache::lonnet::metadata($fn,$rwhat.'.default'); +} -# ============================================================================= -# ===================================== Implements an instance of a spreadsheet +# +# new: Make a new spreadsheet +# +sub new { + my $this = shift; + my $class = ref($this) || $this; + # + my ($uname,$udom,$stype,$usymb)=@_; + # + my $self = { + uname => $uname, + udom => $udom, + type => $stype, + usymb => $usymb, + errorlog => '', + maxrow => '', + mapid => $ENV{'form.mapid'}, + resid => $ENV{'form.resid'}, + cid => $ENV{'request.course.id'}, + csec => $Section{$uname.':'.$udom}, + cnum => $ENV{'course.'.$ENV{'request.course.id'}.'.num'}, + cdom => $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}, + chome => $ENV{'course.'.$ENV{'request.course.id'}.'.home'}, + coursefilename => $ENV{'request.course.fn'}, + coursedesc => $ENV{'course.'.$ENV{'request.course.id'}.'.description'}, + rows => [], + template_cells => [], + }; + $self->{'uhome'} = &Apache::lonnet::homeserver($uname,$udom); + # + # + $self->{'formulas'} = {}; + $self->{'constants'} = {}; + $self->{'othersheets'} = []; + $self->{'rowlabel'} = {}; + # + # + $self->{'safe'} = &initsheet($self->{'type'}); + $self->{'root'} = $self->{'safe'}->root(); + # + # Place some of the %$self items into the safe space except the safe space + # itself + my $initstring = ''; + foreach (qw/uname udom type usymb cid csec coursefilename + cnum cdom chome uhome/) { + $initstring.= qq{\$$_="$self->{$_}";}; + } + $self->{'safe'}->reval($initstring); + bless($self,$class); + return $self; +} ## ## mask - used to reside in the safe space. ## +{ + +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-Za-z]|\*)(\d+|\*)/); @@ -195,81 +867,16 @@ sub mask { } } } - return '^'.$alpha.$num."\$"; + my $expression ='^'.$alpha.$num."\$"; + $memoizer{$key} = $expression; + return $expression; } -sub initsheet { - 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(\&Apache::lonspreadsheet::mask,$safeeval,'&mask'); - $safeeval->share('$@'); - my $code=<<'ENDDEFS'; -# ---------------------------------------------------- Inside of the safe space - -# -# f: formulas -# t: intermediate format (variable references expanded) -# v: output values -# 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 %f; # Holds the formulas for each cell. This is the users - # (spreadsheet authors) data for each cell. - # set by &setformulas and returned by &getformulas - # &setformulas is called by &readsheet, &tmpread, &updateclasssheet, - # &updatestudentassesssheet, &loadstudent, &loadcourse - # &getformulas is called by &writesheet, &tmpwrite, &updateclasssheet, - # &updatestudentassesssheet, &loadstudent, &loadcourse, &loadassessment, -undef %c; # Holds the constants for a sheet. In the assessment - # sheets, this is the A column. Used in &MINPARM, &MAXPARM, &expandnamed, - # &sett, and &setconstants. There is no &getconstants. - # &setconstants is called by &loadstudent, &loadcourse, &load assessment, -undef @os; # Holds the names of other spreadsheets - this is used to specify - # the spreadsheets that are available for the assessment sheet. - # Set by &setothersheets. &setothersheets is called by &handler. A - # related subroutine is &othersheets. -#$errorlog = ''; - -$maxrow = 0; -$sheettype = ''; - -# filename/reference of the sheet -$filename = ''; - -# user data -$uname = ''; -$uhome = ''; -$udom = ''; - -# course data - -$csec = ''; -$chome= ''; -$cnum = ''; -$cdom = ''; -$cid = ''; -$coursefilename = ''; - -# symb - -$usymb = ''; - -# error messages -$errormsg = ''; - +} +sub add_hash_to_safe { + my $self = shift; + my $code = <<'END'; #------------------------------------------------------- =item UWCALC(hashname,modules,units,date) @@ -536,6 +1143,75 @@ sub HASH { } return $Values[-1]; } +END + $self->{'safe'}->reval($code); + return; +} + +sub initsheet { + 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'; +# ---------------------------------------------------- Inside of the safe space +# +# f: formulas +# t: intermediate format (variable references expanded) +# v: output values +# 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 %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 + # sheets, this is the A column. Used in &MINPARM, &MAXPARM, &expandnamed, + # &sett, and &constants. There is no &getconstants. + # &constants is called by &loadstudent, &loadcourse, &load assessment, +undef @os; # Holds the names of other spreadsheets - this is used to specify + # the spreadsheets that are available for the assessment sheet. + # Set by &setothersheets. &setothersheets is called by &handler. A + # related subroutine is &othersheets. +$errorlog = ''; + +$maxrow = 0; +$type = ''; + +# filename/reference of the sheet +$filename = ''; + +# user data +$uname = ''; +$uhome = ''; +$udom = ''; + +# course data + +$csec = ''; +$chome= ''; +$cnum = ''; +$cdom = ''; +$cid = ''; +$coursefilename = ''; + +# symb + +$usymb = ''; + +# error messages +$errormsg = ''; #------------------------------------------------------- @@ -547,14 +1223,14 @@ returns the number of items in the range #------------------------------------------------------- sub NUM { - my $mask=mask(@_); + my $mask=&mask(@_); my $num= $#{@{grep(/$mask/,keys(%sheet_values))}}+1; return $num; } sub BIN { my ($low,$high,$lower,$upper)=@_; - my $mask=mask($lower,$upper); + my $mask=&mask($lower,$upper); my $num=0; foreach (grep /$mask/,keys(%sheet_values)) { if (($sheet_values{$_}>=$low) && ($sheet_values{$_}<=$high)) { @@ -575,7 +1251,7 @@ returns the sum of items in the range. #------------------------------------------------------- sub SUM { - my $mask=mask(@_); + my $mask=&mask(@_); my $sum=0; foreach (grep /$mask/,keys(%sheet_values)) { $sum+=$sheet_values{$_}; @@ -593,7 +1269,8 @@ compute the average of the items in the #------------------------------------------------------- sub MEAN { - my $mask=mask(@_); + my $mask=&mask(@_); +# $errorlog.='(mask = '.$mask.' )'; my $sum=0; my $num=0; foreach (grep /$mask/,keys(%sheet_values)) { @@ -617,7 +1294,8 @@ compute the standard deviation of the it #------------------------------------------------------- sub STDDEV { - my $mask=mask(@_); + my $mask=&mask(@_); +# $errorlog.='(mask = '.$mask.' )'; my $sum=0; my $num=0; foreach (grep /$mask/,keys(%sheet_values)) { $sum+=$sheet_values{$_}; @@ -642,7 +1320,7 @@ compute the product of the items in the #------------------------------------------------------- sub PROD { - my $mask=mask(@_); + my $mask=&mask(@_); my $prod=1; foreach (grep /$mask/,keys(%sheet_values)) { $prod*=$sheet_values{$_}; @@ -660,7 +1338,7 @@ compute the maximum of the items in the #------------------------------------------------------- sub MAX { - my $mask=mask(@_); + my $mask=&mask(@_); my $max='-'; foreach (grep /$mask/,keys(%sheet_values)) { unless ($max) { $max=$sheet_values{$_}; } @@ -679,7 +1357,7 @@ compute the minimum of the items in the #------------------------------------------------------- sub MIN { - my $mask=mask(@_); + my $mask=&mask(@_); my $min='-'; foreach (grep /$mask/,keys(%sheet_values)) { unless ($max) { $max=$sheet_values{$_}; } @@ -702,7 +1380,7 @@ compute the sum of the largest 'num' ite #------------------------------------------------------- sub SUMMAX { my ($num,$lower,$upper)=@_; - my $mask=mask($lower,$upper); + my $mask=&mask($lower,$upper); my @inside=(); foreach (grep /$mask/,keys(%sheet_values)) { push (@inside,$sheet_values{$_}); @@ -727,7 +1405,7 @@ compute the sum of the smallest 'num' it #------------------------------------------------------- sub SUMMIN { my ($num,$lower,$upper)=@_; - my $mask=mask($lower,$upper); + my $mask=&mask($lower,$upper); my @inside=(); foreach (grep /$mask/,keys(%sheet_values)) { $inside[$#inside+1]=$sheet_values{$_}; @@ -786,20 +1464,58 @@ sub MAXPARM { return $max; } -#-------------------------------------------------------- +sub calc { +# $errorlog .= "\%t has ".(keys(%t))." keys\n"; + %sheet_values = %t; # copy %t into %sheet_values. +# $errorlog .= "\%sheet_values has ".(keys(%sheet_values))." keys\n"; + my $notfinished=1; + my $lastcalc=''; + my $depth=0; + while ($notfinished) { + $notfinished=0; + while (my ($cell,$value) = each(%t)) { + my $old=$sheet_values{$cell}; + $sheet_values{$cell}=eval $value; + if ($@) { + undef %sheet_values; + return $cell.': '.$@; + } + if ($sheet_values{$cell} ne $old) { + $notfinished=1; + $lastcalc=$cell; + } + } + $depth++; + if ($depth>100) { + undef %sheet_values; + return $lastcalc.': Maximum calculation depth exceeded'; + } + } + return ''; +} + +# ------------------------------------------- End of "Inside of the safe space" +ENDDEFS + $safeeval->reval($code); + return $safeeval; +} + + +# +# expandnamed used to reside in the safe space +# sub expandnamed { + my $self = shift; my $expression=shift; if ($expression=~/^\&/) { my ($func,$var,$formula)=($expression=~/^\&(\w+)\(([^\;]+)\;(.*)\)/); my @vars=split(/\W+/,$formula); my %values=(); - undef %values; - foreach ( @vars ) { - my $varname=$_; + foreach my $varname ( @vars ) { if ($varname=~/\D/) { $formula=~s/$varname/'$c{\''.$varname.'\'}'/ge; $varname=~s/$var/\(\\w\+\)/g; - foreach (keys(%c)) { + foreach (keys(%{$self->{'constants'}})) { if ($_=~/$varname/) { $values{$1}=1; } @@ -829,18 +1545,21 @@ sub expandnamed { my @matches = (); $#matches = -1; study $expression; - foreach $parameter (keys(%c)) { + my $parameter; + foreach $parameter (keys(%{$self->{'constants'}})) { push @matches,$parameter if ($parameter =~ /$expression/); } if (scalar(@matches) == 0) { $returnvalue = 'unmatched parameter: '.$parameter; } elsif (scalar(@matches) == 1) { + # why do we not do this lookup here, instead of delaying it? $returnvalue = '$c{\''.$matches[0].'\'}'; } elsif (scalar(@matches) > 0) { # more than one match. Look for a concise one $returnvalue = "'non-unique parameter name : $expression'"; foreach (@matches) { if (/^$expression$/) { + # why do we not do this lookup here? $returnvalue = '$c{\''.$_.'\'}'; } } @@ -853,27 +1572,26 @@ sub expandnamed { } } +# +# sett used to reside in the safe space +# sub sett { - %t=(); + my $self = shift; + my %t=(); my $pattern=''; - if ($sheettype eq 'assesscalc') { + if ($self->{'type'} eq 'assesscalc') { $pattern='A'; } else { $pattern='[A-Z]'; } # Deal with the template row - foreach (keys(%f)) { - next if ($_!~/template\_(\w)/); - my $col=$1; + foreach my $col ($self->template_cells()) { next if ($col=~/^$pattern/); - foreach (keys(%f)) { - next if ($_!~/A(\d+)/); - my $trow=$1; - next if (! $trow); + foreach my $trow ($self->rows()) { # Get the name of this cell my $lb=$col.$trow; # Grab the template declaration - $t{$lb}=$f{'template_'.$col}; + $t{$lb}=$self->formula('template_'.$col); # Replace '#' with the row number $t{$lb}=~s/\#/$trow/g; # Replace '....' with ',' @@ -881,139 +1599,125 @@ sub sett { # Replace 'A0' with the value from 'A0' $t{$lb}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g; # Replace parameters - $t{$lb}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge; + $t{$lb}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.$self->expandnamed($2)/ge; } } # Deal with the normal cells - foreach (keys(%f)) { - if (exists($f{$_}) && ($_!~/template\_/)) { - my $matches=($_=~/^$pattern(\d+)/); - if (($matches) && ($1)) { - unless ($f{$_}=~/^\!/) { - $t{$_}=$c{$_}; - } - } else { - $t{$_}=$f{$_}; - $t{$_}=~s/\.\.+/\,/g; - $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g; - $t{$_}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge; + foreach ($self->formulas_keys()) { + next if ($_=~/template\_/); + if (($_=~/^$pattern(\d+)/) && ($1)) { + if ($self->formula($_) !~ /^\!/) { + $t{$_}=$self->{'constants'}->{$_}; } + } else { + $t{$_}=$self->formula($_); + $t{$_}=~s/\.\.+/\,/g; + $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g; + $t{$_}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.$self->expandnamed($2)/ge; } } # For inserted lines, [B-Z] is also valid - unless ($sheettype eq 'assesscalc') { - foreach (keys(%f)) { - if ($_=~/[B-Z](\d+)/) { - if ($f{'A'.$1}=~/^[\~\-]/) { - $t{$_}=$f{$_}; - $t{$_}=~s/\.\.+/\,/g; - $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g; - $t{$_}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge; - } - } - } + if ($self->{'type'} ne 'assesscalc') { + foreach ($self->formulas_keys()) { + next if ($_ !~ /[B-Z](\d+)/); + next if ($self->formula('A'.$1) !~ /^[\~\-]/); + $t{$_}=$self->formula($_); + $t{$_}=~s/\.\.+/\,/g; + $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g; + $t{$_}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.$self->expandnamed($2)/ge; + } } # For some reason 'A0' gets special treatment... This seems superfluous # but I imagine it is here for a reason. - $t{'A0'}=$f{'A0'}; + $t{'A0'}=$self->formula('A0'); $t{'A0'}=~s/\.\.+/\,/g; $t{'A0'}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g; - $t{'A0'}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge; + $t{'A0'}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.$self->expandnamed($2)/ge; + # Put %t into the safe space + %{$self->{'safe'}->varglob('t')}=%t; } -sub calc { - undef %sheet_values; - &sett(); - my $notfinished=1; - my $lastcalc=''; - my $depth=0; - while ($notfinished) { - $notfinished=0; - foreach (keys(%t)) { - #$errorlog .= "$_:".$t{$_}; - my $old=$sheet_values{$_}; - $sheet_values{$_}=eval $t{$_}; - if ($@) { - undef %sheet_values; - return $_.': '.$@; - } - if ($sheet_values{$_} ne $old) { $notfinished=1; $lastcalc=$_; } - #$errorlog .= ":".$sheet_values{$_}."\n"; - } - $depth++; - if ($depth>100) { - undef %sheet_values; - return $lastcalc.': Maximum calculation depth exceeded'; - } - } - return ''; -} - -# ------------------------------------------- End of "Inside of the safe space" -ENDDEFS - $safeeval->reval($code); - return $safeeval; -} +########################################### +### Row output routines ### +########################################### # -# +# get_row: Produce output row n from sheet by calling the appropriate routine # +sub get_row { + my $self = shift; + my ($n) = @_; + my ($rowlabel,@rowdata); + if ($n eq '-') { + ($rowlabel,@rowdata) = $self->templaterow(); + } elsif ($self->{'type'} eq 'studentcalc') { + ($rowlabel,@rowdata) = $self->outrowassess($n); + } else { + ($rowlabel,@rowdata) = $self->outrow($n); + } + return ($rowlabel,@rowdata); +} + sub templaterow { - my $sheet = shift; + my $self = shift; my @cols=(); - my $rowlabel = 'Template'; - foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M', - 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', - 'a','b','c','d','e','f','g','h','i','j','k','l','m', - 'n','o','p','q','r','s','t','u','v','w','x','y','z') { + my $rowlabel = 'Template</td><td> '; + foreach my $n ('A','B','C','D','E','F','G','H','I','J','K','L','M', + 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', + 'a','b','c','d','e','f','g','h','i','j','k','l','m', + 'n','o','p','q','r','s','t','u','v','w','x','y','z') { push(@cols,{ name => 'template_'.$_, - formula => $sheet->{'f'}->{'template_'.$_}, - value => $sheet->{'f'}->{'template_'.$_} }); + formula => $self->formula('template_'.$n), + value => $self->value('template_'.$n) }); } return ($rowlabel,@cols); } sub outrowassess { + my $self = shift; # $n is the current row number - my ($sheet,$n) = @_; + my ($n) = @_; my @cols=(); my $rowlabel=''; if ($n) { - my ($usy,$ufn)=split(/__&&&\__/,$sheet->{'f'}->{'A'.$n}); - if (exists($sheet->{'rowlabel'}->{$usy})) { + my ($usy,$ufn)=split(/__&&&\__/,$self->formula('A'.$n)); + if (exists($self->{'rowlabel'}->{$usy})) { # This is dumb, but we need the information when we output # the html version of the studentcalc spreadsheet for the # links to the assesscalc sheets. - $rowlabel = $sheet->{'rowlabel'}->{$usy}.':'. + $rowlabel = $self->{'rowlabel'}->{$usy}.':'. &Apache::lonnet::escape($ufn); } else { $rowlabel = ''; } + } elsif ($ENV{'request.role'} =~ /^st\./) { + $rowlabel = 'Summary</td><td>0'; } else { - $rowlabel = 'Export'; + $rowlabel = 'Export</td><td>0'; } foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M', 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', 'a','b','c','d','e','f','g','h','i','j','k','l','m', 'n','o','p','q','r','s','t','u','v','w','x','y','z') { push(@cols,{ name => $_.$n, - formula => $sheet->{'f'}->{$_.$n}, - value => $sheet->{'values'}->{$_.$n}}); + formula => $self->formula($_.$n), + value => $self->value($_.$n)}); } return ($rowlabel,@cols); } sub outrow { - my ($sheet,$n)=@_; + my $self = shift; + my ($n)=@_; my @cols=(); my $rowlabel; if ($n) { - $rowlabel = $sheet->{'rowlabel'}->{$sheet->{'f'}->{'A'.$n}}; + $rowlabel = $self->{'rowlabel'}->{$self->formula('A'.$n)}; } else { - if ($sheet->{'sheettype'} eq 'classcalc') { - $rowlabel = 'Summary'; + if ($self->{'type'} eq 'classcalc') { + $rowlabel = 'Summary</td><td>0'; } else { - $rowlabel = 'Export'; + $rowlabel = 'Export</td><td>0'; } } foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M', @@ -1021,94 +1725,262 @@ sub outrow { 'a','b','c','d','e','f','g','h','i','j','k','l','m', 'n','o','p','q','r','s','t','u','v','w','x','y','z') { push(@cols,{ name => $_.$n, - formula => $sheet->{'f'}->{$_.$n}, - value => $sheet->{'values'}->{$_.$n}}); + formula => $self->formula($_.$n), + value => $self->value($_.$n)}); } return ($rowlabel,@cols); } -# ------------------------------------------------ Add or change formula values -sub setformulas { - my ($sheet)=shift; - %{$sheet->{'safe'}->varglob('f')}=%{$sheet->{'f'}}; +######################################################## +#### Spreadsheet calculation methods ##### +######################################################## +# +# calcsheet: makes all the calls to compute the spreadsheet. +# +sub calcsheet { + my $self = shift; + $self->sync_safe_space(); + $self->clear_errorlog(); + $self->sett(); + my $result = $self->{'safe'}->reval('&calc();'); + %{$self->{'values'}} = %{$self->{'safe'}->varglob('sheet_values')}; + return $result; } -# ------------------------------------------------ Add or change formula values -sub setconstants { - my ($sheet)=shift; +## +## sync_safe_space: Called by calcsheet to make sure all the data we +# need to calculate is placed into the safe space +## +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'}}; + # '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'}}; +} + +## +## Retrieve the error log from the safe space (used for debugging) +## +sub get_errorlog { + my $self = shift; + $self->{'errorlog'} = ${$self->{'safe'}->varglob('errorlog')}; + return $self->{'errorlog'}; +} + +## +## Clear the error log inside the safe space +## +sub clear_errorlog { + my $self = shift; + ${$self->{'safe'}->varglob('errorlog')} = ''; + $self->{'errorlog'} = ''; +} + + +######################################################## +#### Spreadsheet content retrieval/setting methods ##### +######################################################## +## +## constants: either set or get the constants +## +## +sub constants { + my $self=shift; my ($constants) = @_; - if (! ref($constants)) { - my %tmp = @_; - $constants = \%tmp; + if (defined($constants)) { + if (! ref($constants)) { + my %tmp = @_; + $constants = \%tmp; + } + $self->{'constants'} = $constants; + return; + } else { + return %{$self->{'constants'}}; + } +} + +## +## formulas: either set or get the formulas +## +sub formulas { + my $self=shift; + my ($formulas) = @_; + if (defined($formulas)) { + if (! ref($formulas)) { + my %tmp = @_; + $formulas = \%tmp; + } + $self->{'formulas'} = $formulas; + $self->{'rows'} = []; + $self->{'template_cells'} = []; + return; + } else { + return %{$self->{'formulas'}}; } - $sheet->{'constants'} = $constants; - return %{$sheet->{'safe'}->varglob('c')}=%{$sheet->{'constants'}}; } -# --------------------------------------------- Set names of other spreadsheets -sub setothersheets { - my $sheet = shift; - my @othersheets = @_; - $sheet->{'othersheets'} = \@othersheets; - @{$sheet->{'safe'}->varglob('os')}=@othersheets; +## +## formulas_keys: Return the keys to the formulas hash. +## +sub formulas_keys { + my $self = shift; + my @keys = keys(%{$self->{'formulas'}}); + return keys(%{$self->{'formulas'}}); +} + +## +## formula: Return the formula for a given cell in the spreadsheet +## returns '' if the cell does not have a formula or does not exist +## +sub formula { + my $self = shift; + my $cell = shift; + if (defined($cell) && exists($self->{'formulas'}->{$cell})) { + return $self->{'formulas'}->{$cell}; + } + return ''; +} + +## +## logthis: write the input to lonnet.log +## +sub logthis { + my $self = shift; + my $message = shift; + &Apache::lonnet::logthis($self->{'type'}.':'. + $self->{'uname'}.':'.$self->{'udom'}.':'. + $message); return; } -# ------------------------------------------------ Add or change formula values -sub setrowlabels { - my $sheet=shift; - my ($rowlabel) = @_; - if (! ref($rowlabel)) { - my %tmp = @_; - $rowlabel = \%tmp; +## +## dump_formulas_to_log: makes lonnet.log huge... +## +sub dump_formulas_to_log { + my $self =shift; + $self->logthis("Spreadsheet formulas"); + $self->logthis("--------------------------------------------------------"); + while (my ($cell, $formula) = each(%{$self->{'formulas'}})) { + $self->logthis(' '.$cell.' = '.$formula); + } + $self->logthis("--------------------------------------------------------");} + +## +## value: returns the computed value of a particular cell +## +sub value { + my $self = shift; + my $cell = shift; + if (defined($cell) && exists($self->{'values'}->{$cell})) { + return $self->{'values'}->{$cell}; } - $sheet->{'rowlabel'}=$rowlabel; + return ''; } -# ------------------------------------------------------- Calculate spreadsheet -sub calcsheet { - my $sheet=shift; - my $result = $sheet->{'safe'}->reval('&calc();'); - %{$sheet->{'values'}} = %{$sheet->{'safe'}->varglob('sheet_values')}; - return $result; +## +## dump_values_to_log: makes lonnet.log huge... +## +sub dump_values_to_log { + my $self =shift; + $self->logthis("Spreadsheet Values"); + $self->logthis("--------------------------------------------------------"); + while (my ($cell, $value) = each(%{$self->{'values'}})) { + $self->logthis(' '.$cell.' = '.$value); + } + $self->logthis("--------------------------------------------------------");} + +################################ +## Helper functions ## +################################ +## +## rebuild_stats: rebuilds the rows and template_cells arrays +## +sub rebuild_stats { + my $self = shift; + $self->{'rows'}=[]; + $self->{'template_cells'}=[]; + foreach my $cell($self->formulas_keys()) { + push(@{$self->{'rows'}},$1) if ($cell =~ /^A(\d+)/ && $1 != 0); + push(@{$self->{'template_cells'}},$1) if ($cell =~ /^template_(\w+)/); + } + return; +} + +## +## template_cells returns a list of the cells defined in the template row +## +sub template_cells { + my $self = shift; + $self->rebuild_stats() if (!@{$self->{'template_cells'}}); + return @{$self->{'template_cells'}}; +} + +## +## rows returns a list of the names of cells defined in the A column +## +sub rows { + my $self = shift; + $self->rebuild_stats() if (!@{$self->{'rows'}}); + return @{$self->{'rows'}}; } -# ---------------------------------------------------------------- Get formulas -# Return a copy of the formulas -sub getformulas { - my $sheet = shift; - return %{$sheet->{'safe'}->varglob('f')}; +## +## Sigh.... +## +sub setothersheets { + my $self = shift; + my @othersheets = @_; + $self->{'othersheets'} = \@othersheets; } -sub geterrorlog { - my $sheet = shift; - return ${$sheet->{'safe'}->varglob('errorlog')}; +## +## rowlabels: get or set the rowlabels hash from the spreadsheet. +## +sub rowlabels { + my $self = shift; + my ($rowlabel) = @_; + if (defined($rowlabel)) { + if (! ref($rowlabel)) { + my %tmp = @_; + $rowlabel = \%tmp; + } + $self->{'rowlabel'}=$rowlabel; + return; + } else { + return %{$self->{'rowlabel'}} if (defined($self->{'rowlabels'})); + } } +## +## gettitle: returns a title for the spreadsheet. +## sub gettitle { - my $sheet = shift; - if ($sheet->{'sheettype'} eq 'classcalc') { - return $sheet->{'coursedesc'}; - } elsif ($sheet->{'sheettype'} eq 'studentcalc') { - return 'Grades for '.$sheet->{'uname'}.'@'.$sheet->{'udom'}; - } elsif ($sheet->{'sheettype'} eq 'assesscalc') { - if (($sheet->{'usymb'} eq '_feedback') || - ($sheet->{'usymb'} eq '_evaluation') || - ($sheet->{'usymb'} eq '_discussion') || - ($sheet->{'usymb'} eq '_tutoring')) { - my $title = $sheet->{'usymb'}; + my $self = shift; + if ($self->{'type'} eq 'classcalc') { + return $self->{'coursedesc'}; + } elsif ($self->{'type'} eq 'studentcalc') { + return 'Grades for '.$self->{'uname'}.'@'.$self->{'udom'}; + } elsif ($self->{'type'} eq 'assesscalc') { + if (($self->{'usymb'} eq '_feedback') || + ($self->{'usymb'} eq '_evaluation') || + ($self->{'usymb'} eq '_discussion') || + ($self->{'usymb'} eq '_tutoring')) { + my $title = $self->{'usymb'}; $title =~ s/^_//; $title = ucfirst($title); return $title; } - return if (! defined($sheet->{'mapid'}) || - $sheet->{'mapid'} !~ /^\d+$/); - my $mapid = $sheet->{'mapid'}; - return if (! defined($sheet->{'resid'}) || - $sheet->{'resid'} !~ /^\d+$/); - my $resid = $sheet->{'resid'}; + return if (! defined($self->{'mapid'}) || + $self->{'mapid'} !~ /^\d+$/); + my $mapid = $self->{'mapid'}; + return if (! defined($self->{'resid'}) || + $self->{'resid'} !~ /^\d+$/); + my $resid = $self->{'resid'}; my %course_db; - tie(%course_db,'GDBM_File',$sheet->{'coursefilename'}.'.db', + tie(%course_db,'GDBM_File',$self->{'coursefilename'}.'.db', &GDBM_READER(),0640); return if (! tied(%course_db)); my $key = 'title_'.$mapid.'.'.$resid; @@ -1116,94 +1988,68 @@ sub gettitle { if (exists($course_db{$key})) { $title = $course_db{$key}; } else { - $title = $sheet->{'usymb'}; + $title = $self->{'usymb'}; } untie (%course_db); return $title; } } -# ----------------------------------------------------- Get value of $f{'A'.$n} -sub getfa { - my $sheet = shift; - my ($n)=@_; - return $sheet->{'safe'}->reval('$f{"A'.$n.'"}'); -} - -# ------------------------------------------------------------- Export of A-row +# +# Export of A-row +# sub exportdata { - my $sheet=shift; + my $self=shift; my @exportarray=(); foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M', 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') { - if (exists($sheet->{'values'}->{$_.'0'})) { - push(@exportarray,$sheet->{'values'}->{$_.'0'}); - } else { - push(@exportarray,''); - } + push(@exportarray,$self->value($_.'0')); } return @exportarray; } - - +## +## update_student_sheet: experimental function +## sub update_student_sheet{ - my ($sheet,$r,$c) = @_; + my $self = shift; + my ($r,$c) = @_; # Load in the studentcalc sheet - &readsheet($sheet,'default_studentcalc'); + $self->readsheet('default_studentcalc'); # Determine the structure (contained assessments, etc) of the sheet - &updatesheet($sheet); + $self->updatesheet(); # Load in the cached sheets for this student - &cachedssheets($sheet); + $self->cachedssheets(); # Load in the (possibly cached) data from the assessment sheets - &loadstudent($sheet,$r,$c); + $self->loadstudent($r,$c); # Compute the sheet - &calcsheet($sheet); + $self->calcsheet(); } -# ========================================================== End of Spreadsheet -# ============================================================================= # -# Procedures for spreadsheet output +# sort_indicies: returns an ordered list of the rows of the spreadsheet # -# --------------------------------------------- Produce output row n from sheet - -sub get_row { - my ($sheet,$n) = @_; - my ($rowlabel,@rowdata); - if ($n eq '-') { - ($rowlabel,@rowdata) = &templaterow($sheet); - } elsif ($sheet->{'sheettype'} eq 'studentcalc') { - ($rowlabel,@rowdata) = &outrowassess($sheet,$n); - } else { - ($rowlabel,@rowdata) = &outrow($sheet,$n); - } - return ($rowlabel,@rowdata); -} - -######################################################################## -######################################################################## sub sort_indicies { - my $sheet = shift; + my $self = shift; my @sortidx=(); # - if ($sheet->{'sheettype'} eq 'classcalc') { + if ($self->{'type'} eq 'classcalc') { my @sortby=(undef); # Skip row 0 - for (my $row=1;$row<=$sheet->{'maxrow'};$row++) { + for (my $row=1;$row<=$self->{'maxrow'};$row++) { my (undef,$sname,$sdom,$fullname,$section,$id) = - split(':',$sheet->{'rowlabel'}->{$sheet->{'f'}->{'A'.$row}}); + split(':',$self->{'rowlabel'}->{$self->formula('A'.$row)}); push (@sortby, lc($fullname)); push (@sortidx, $row); } @sortidx = sort { $sortby[$a] cmp $sortby[$b]; } @sortidx; - } elsif ($sheet->{'sheettype'} eq 'studentcalc') { + } elsif ($self->{'type'} eq 'studentcalc') { my @sortby1=(undef); my @sortby2=(undef); # Skip row 0 - for (my $row=1;$row<=$sheet->{'maxrow'};$row++) { - my ($key,undef) = split(/__&&&\__/,$sheet->{'f'}->{'A'.$row}); - my $rowlabel = $sheet->{'rowlabel'}->{$key}; + for (my $row=1;$row<=$self->{'maxrow'};$row++) { + my ($key,undef) = split(/__&&&\__/,$self->formula('A'.$row)); + my $rowlabel = $self->{'rowlabel'}->{$key}; my (undef,$symb,$mapid,$resid,$title,$ufn) = split(':',$rowlabel); $ufn = &Apache::lonnet::unescape($ufn); @@ -1222,8 +2068,9 @@ sub sort_indicies { } else { my @sortby=(undef); # Skip row 0 - for (my $row=1;$row<=$sheet->{'maxrow'};$row++) { - push (@sortby, $sheet->{'safe'}->reval('$f{"A'.$row.'"}')); + $self->sync_safe_space(); + for (my $row=1;$row<=$self->{'maxrow'};$row++) { + push (@sortby, $self->{'safe'}->reval('$f{"A'.$row.'"}')); push (@sortidx, $row); } @sortidx = sort { $sortby[$a] cmp $sortby[$b]; } @sortidx; @@ -1259,7 +2106,7 @@ sub html_editable_cell { } elsif ($value =~ /^\s*$/ ) { $value = '<font color="'.$bgcolor.'">#</font>'; } else { - $value = &HTML::Entities::encode($value); + $value = &HTML::Entities::encode($value) if ($value !~/ /); } # Make the formula safe for outputting $formula =~ s/\'/\"/g; @@ -1277,23 +2124,24 @@ sub html_editable_cell { sub html_uneditable_cell { my ($cell,$bgcolor) = @_; my $value = (defined($cell) ? $cell->{'value'} : ''); - $value = &HTML::Entities::encode($value); + $value = &HTML::Entities::encode($value) if ($value !~/ /); return ' '.$value.' '; } sub outsheet_html { - my ($sheet,$r) = @_; + my $self = shift; + my ($r) = @_; my ($num_uneditable,$realm,$row_type); my $requester_is_student = ($ENV{'request.role'} =~ /^st\./); - if ($sheet->{'sheettype'} eq 'assesscalc') { + if ($self->{'type'} eq 'assesscalc') { $num_uneditable = 1; $realm = 'Assessment'; $row_type = 'Item'; - } elsif ($sheet->{'sheettype'} eq 'studentcalc') { + } elsif ($self->{'type'} eq 'studentcalc') { $num_uneditable = 26; $realm = 'User'; $row_type = 'Assessment'; - } elsif ($sheet->{'sheettype'} eq 'classcalc') { + } elsif ($self->{'type'} eq 'classcalc') { $num_uneditable = 26; $realm = 'Course'; $row_type = 'Student'; @@ -1307,7 +2155,7 @@ sub outsheet_html { my $tabledata =<<"END"; <table border="2"> <tr> - <th colspan="1" rowspan="2"><font size="+2">$realm</font></th> + <th colspan="2" rowspan="2"><font size="+2">$realm</font></th> <td bgcolor="#FFDDDD" colspan="$num_uneditable"> <b><font size="+1">Import</font></b></td> <td colspan="$num_left"> @@ -1332,8 +2180,8 @@ END my ($num_cols_output,$row_html,$rowlabel,@rowdata); if (! $requester_is_student) { - ($rowlabel,@rowdata) = &get_row($sheet,'-'); - $row_html = '<tr><td>'.&format_html_rowlabel($sheet,$rowlabel).'</td>'; + ($rowlabel,@rowdata) = $self->get_row('-'); + $row_html = '<tr><td>'.$self->format_html_rowlabel($rowlabel).'</td>'; $num_cols_output = 0; foreach my $cell (@rowdata) { if ($requester_is_student || @@ -1352,8 +2200,8 @@ END #################################### # Print out summary/export row #################################### - ($rowlabel,@rowdata) = &get_row($sheet,'0'); - $row_html = '<tr><td>'.&format_html_rowlabel($sheet,'Summary').'</td>'; + ($rowlabel,@rowdata) = $self->get_row('0'); + $row_html = '<tr><td>'.$self->format_html_rowlabel($rowlabel).'</td>'; $num_cols_output = 0; foreach my $cell (@rowdata) { if ($num_cols_output++ < 26 && ! $requester_is_student) { @@ -1371,18 +2219,18 @@ END #################################### # Prepare to output rows #################################### - my @Rows = &sort_indicies($sheet); + my @Rows = $self->sort_indicies(); # # Loop through the rows and output them one at a time my $rows_output=0; foreach my $rownum (@Rows) { - my ($rowlabel,@rowdata) = &get_row($sheet,$rownum); + my ($rowlabel,@rowdata) = $self->get_row($rownum); next if ($rowlabel =~ /^\s*$/); - next if (($sheet->{'sheettype'} eq 'assesscalc') && + next if (($self->{'type'} eq 'assesscalc') && (! $ENV{'form.showall'}) && ($rowdata[0]->{'value'} =~ /^\s*$/)); if (! $ENV{'form.showall'} && - $sheet->{'sheettype'} =~ /^(studentcalc|classcalc)$/) { + $self->{'type'} =~ /^(studentcalc|classcalc)$/) { my $row_is_empty = 1; foreach my $cell (@rowdata) { if ($cell->{'value'} !~ /^\s*$/) { @@ -1398,20 +2246,19 @@ END my $row_html ="\n".'<tr><td><b><font size=+1>'.$rownum. '</font></b></td>'; # - if ($sheet->{'sheettype'} eq 'classcalc') { - $row_html.='<td>'.&format_html_rowlabel($sheet,$rowlabel).'</td>'; + if ($self->{'type'} eq 'classcalc') { + $row_html.='<td>'.$self->format_html_rowlabel($rowlabel).'</td>'; # Output links for each student? # Nope, that is already done for us in format_html_rowlabel # (for now) - } elsif ($sheet->{'sheettype'} eq 'studentcalc') { + } elsif ($self->{'type'} eq 'studentcalc') { my $ufn = (split(/:/,$rowlabel))[5]; - $row_html.='<td>'.&format_html_rowlabel($sheet,$rowlabel); + $row_html.='<td>'.$self->format_html_rowlabel($rowlabel); $row_html.= '<br>'. '<select name="sel_'.$rownum.'" '. 'onChange="changesheet('.$rownum.')">'. '<option name="default">Default</option>'; - - foreach (@{$sheet->{'othersheets'}}) { + foreach (@{$self->{'othersheets'}}) { $row_html.='<option name="'.$_.'"'; if ($ufn eq $_) { $row_html.=' selected'; @@ -1419,8 +2266,8 @@ END $row_html.='>'.$_.'</option>'; } $row_html.='</select></td>'; - } elsif ($sheet->{'sheettype'} eq 'assesscalc') { - $row_html.='<td>'.&format_html_rowlabel($sheet,$rowlabel).'</td>'; + } elsif ($self->{'type'} eq 'assesscalc') { + $row_html.='<td>'.$self->format_html_rowlabel($rowlabel).'</td>'; } # my $shown_cells = 0; @@ -1468,7 +2315,7 @@ END # Debugging code (be sure to uncomment errorlog code in safe space): # # $r->print("\n<pre>"); - # $r->print(&geterrorlog($sheet)); + # $r->print(&geterrorlog($self)); # $r->print("\n</pre>"); return 1; } @@ -1477,20 +2324,21 @@ END ## csv output routines ## ############################################ sub outsheet_csv { - my ($sheet,$r) = @_; + my $self = shift; + my ($r) = @_; my $csvdata = ''; my @Values; #################################### # Prepare to output rows #################################### - my @Rows = &sort_indicies($sheet); + my @Rows = $self->sort_indicies(); # # Loop through the rows and output them one at a time my $rows_output=0; foreach my $rownum (@Rows) { - my ($rowlabel,@rowdata) = &get_row($sheet,$rownum); + my ($rowlabel,@rowdata) = $self->get_row($rownum); next if ($rowlabel =~ /^\s*$/); - push (@Values,&format_csv_rowlabel($sheet,$rowlabel)); + push (@Values,$self->format_csv_rowlabel($rowlabel)); foreach my $cell (@rowdata) { push (@Values,'"'.$cell->{'value'}.'"'); } @@ -1524,17 +2372,18 @@ sub outsheet_csv { ## Excel output routines ## ############################################ sub outsheet_recursive_excel { - my ($sheet,$r) = @_; + my $self = shift; + my ($r) = @_; my $c = $r->connection; - return undef if ($sheet->{'sheettype'} ne 'classcalc'); - my ($workbook,$filename) = &create_excel_spreadsheet($sheet,$r); + return undef if ($self->{'type'} ne 'classcalc'); + my ($workbook,$filename) = $self->create_excel_spreadsheet($r); return undef if (! defined($workbook)); # # Create main worksheet my $main_worksheet = $workbook->addworksheet('main'); # # Figure out who the students are - my %f=&getformulas($sheet); + my %f=$self->formulas(); my $count = 0; $r->print(<<END); <p> @@ -1551,18 +2400,19 @@ A link to the spreadsheet will be availa END $r->rflush(); my $starttime = time; - foreach my $rownum (&sort_indicies($sheet)) { + foreach my $rownum ($self->sort_indicies()) { $count++; my ($sname,$sdom) = split(':',$f{'A'.$rownum}); my $student_excel_worksheet=$workbook->addworksheet($sname.'@'.$sdom); # Create a new spreadsheet - my $studentsheet = &makenewsheet($sname,$sdom,'studentcalc',undef); + my $studentsheet = &Apache::lonspreadsheet::Spreadsheet->new + ($sname,$sdom,'studentcalc',undef); # Read in the spreadsheet definition - &update_student_sheet($studentsheet,$r,$c); + $studentsheet->update_student_sheet($r,$c); # Stuff the sheet into excel - &export_sheet_as_excel($studentsheet,$student_excel_worksheet); - my $totaltime = int((time - $starttime) / $count * $sheet->{'maxrow'}); - my $timeleft = int((time - $starttime) / $count * ($sheet->{'maxrow'} - $count)); + $studentsheet->export_sheet_as_excel($student_excel_worksheet); + my $totaltime = int((time - $starttime) / $count * $self->{'maxrow'}); + my $timeleft = int((time - $starttime) / $count * ($self->{'maxrow'} - $count)); if ($count % 5 == 0) { $r->print($count.' students completed.'. ' Time remaining: '.$timeleft.' sec. '. @@ -1579,7 +2429,7 @@ END $r->rflush(); # # &export_sheet_as_excel fills $worksheet with the data from $sheet - &export_sheet_as_excel($sheet,$main_worksheet); + $self->export_sheet_as_excel($main_worksheet); # $workbook->close(); # Okay, the spreadsheet is taken care of, so give the user a link. @@ -1593,21 +2443,22 @@ END } sub outsheet_excel { - my ($sheet,$r) = @_; - my ($workbook,$filename) = &create_excel_spreadsheet($sheet,$r); + my $self = shift; + my ($r) = @_; + my ($workbook,$filename) = $self->create_excel_spreadsheet($r); return undef if (! defined($workbook)); my $sheetname; - if ($sheet->{'sheettype'} eq 'classcalc') { + if ($self->{'type'} eq 'classcalc') { $sheetname = 'Main'; - } elsif ($sheet->{'sheettype'} eq 'studentcalc') { - $sheetname = $sheet->{'uname'}.'@'.$sheet->{'udom'}; - } elsif ($sheet->{'sheettype'} eq 'assesscalc') { - $sheetname = $sheet->{'uname'}.'@'.$sheet->{'udom'}.' assessment'; + } elsif ($self->{'type'} eq 'studentcalc') { + $sheetname = $self->{'uname'}.'@'.$self->{'udom'}; + } elsif ($self->{'type'} eq 'assesscalc') { + $sheetname = $self->{'uname'}.'@'.$self->{'udom'}.' assessment'; } my $worksheet = $workbook->addworksheet($sheetname); # # &export_sheet_as_excel fills $worksheet with the data from $sheet - &export_sheet_as_excel($sheet,$worksheet); + $self->export_sheet_as_excel($worksheet); # $workbook->close(); # Okay, the spreadsheet is taken care of, so give the user a link. @@ -1617,7 +2468,8 @@ sub outsheet_excel { } sub create_excel_spreadsheet { - my ($sheet,$r) = @_; + my $self = shift; + my ($r) = @_; my $filename = '/prtspool/'. $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'. time.'_'.rand(1000000000).'.xls'; @@ -1630,7 +2482,7 @@ sub create_excel_spreadsheet { return undef; } # - # The spreadsheet stores temporary data in files, then put them + # The excel spreadsheet stores temporary data in files, then put them # together. If needed we should be able to disable this (memory only). # The temporary directory must be specified before calling 'addworksheet'. # File::Temp is used to determine the temporary directory. @@ -1641,7 +2493,7 @@ sub create_excel_spreadsheet { } sub export_sheet_as_excel { - my $sheet = shift; + my $self = shift; my $worksheet = shift; # my $rows_output = 0; @@ -1649,8 +2501,8 @@ sub export_sheet_as_excel { #################################### # Write an identifying row # #################################### - my @Headerinfo = ($sheet->{'coursedesc'}); - my $title = &gettitle($sheet); + my @Headerinfo = ($self->{'coursedesc'}); + my $title = $self->gettitle(); $cols_output = 0; if (defined($title)) { $worksheet->write($rows_output++,$cols_output++,$title); @@ -1658,8 +2510,8 @@ sub export_sheet_as_excel { #################################### # Write the summary/export row # #################################### - my ($rowlabel,@rowdata) = &get_row($sheet,'0'); - my $label = &format_excel_rowlabel($sheet,$rowlabel); + my ($rowlabel,@rowdata) = &get_row($self,'0'); + my $label = &format_excel_rowlabel($self,$rowlabel); $cols_output = 0; $worksheet->write($rows_output,$cols_output++,$label); foreach my $cell (@rowdata) { @@ -1669,16 +2521,16 @@ sub export_sheet_as_excel { #################################### # Prepare to output rows #################################### - my @Rows = &sort_indicies($sheet); + my @Rows = &sort_indicies($self); # # Loop through the rows and output them one at a time foreach my $rownum (@Rows) { - my ($rowlabel,@rowdata) = &get_row($sheet,$rownum); + my ($rowlabel,@rowdata) = &get_row($self,$rownum); next if ($rowlabel =~ /^[\s]*$/); $cols_output = 0; - my $label = &format_excel_rowlabel($sheet,$rowlabel); + my $label = &format_excel_rowlabel($self,$rowlabel); if ( ! $ENV{'form.showall'} && - $sheet->{'sheettype'} =~ /^(studentcalc|classcalc)$/) { + $self->{'type'} =~ /^(studentcalc|classcalc)$/) { my $row_is_empty = 1; foreach my $cell (@rowdata) { if ($cell->{'value'} !~ /^\s*$/) { @@ -1704,7 +2556,8 @@ sub export_sheet_as_excel { ## XML output routines ## ############################################ sub outsheet_xml { - my ($sheet,$r) = @_; + my $self = shift; + my ($r) = @_; ## Someday XML ## Will be rendered for the user ## But not on this day @@ -1714,32 +2567,35 @@ sub outsheet_xml { ## Outsheet - calls other outsheet_* functions ## sub outsheet { - my ($sheet,$r)=@_; + my $self = shift; + my ($r)=@_; if (! exists($ENV{'form.output'})) { $ENV{'form.output'} = 'HTML'; } if (lc($ENV{'form.output'}) eq 'csv') { - &outsheet_csv($sheet,$r); + $self->outsheet_csv($r); } elsif (lc($ENV{'form.output'}) eq 'excel') { - &outsheet_excel($sheet,$r); + $self->outsheet_excel($r); } elsif (lc($ENV{'form.output'}) eq 'recursive excel') { - &outsheet_recursive_excel($sheet,$r); + $self->outsheet_recursive_excel($r); # } elsif (lc($ENV{'form.output'}) eq 'xml' ) { -# &outsheet_xml($sheet,$r); +# $self->outsheet_xml($r); } else { - &outsheet_html($sheet,$r); + $self->outsheet_html($r); } } -######################################################################## -######################################################################## +# +# othersheets: Returns the list of other spreadsheets available +# sub othersheets { - my ($sheet,$stype)=@_; - $stype = $sheet->{'sheettype'} if (! defined($stype)); - # - my $cnum = $sheet->{'cnum'}; - my $cdom = $sheet->{'cdom'}; - my $chome = $sheet->{'chome'}; + my $self = shift; + my ($stype)=@_; + $stype = $self->{'type'} if (! defined($stype)); + # + my $cnum = $self->{'cnum'}; + my $cdom = $self->{'cdom'}; + my $chome = $self->{'chome'}; # my @alternatives=(); my %results=&Apache::lonnet::dump($stype.'_spreadsheets',$cdom,$cnum); @@ -1751,7 +2607,7 @@ sub othersheets { } # -# -------------------------------------- Parse a spreadsheet +# Parse a spreadsheet # sub parse_sheet { # $sheetxml is a scalar reference or a scalar @@ -1778,16 +2634,14 @@ sub parse_sheet { return \%f; } -# -# -------------------------------------- Read spreadsheet formulas for a course -# sub readsheet { - my ($sheet,$fn)=@_; + my $self = shift; + my ($fn)=@_; # - my $stype = $sheet->{'sheettype'}; - my $cnum = $sheet->{'cnum'}; - my $cdom = $sheet->{'cdom'}; - my $chome = $sheet->{'chome'}; + my $stype = $self->{'type'}; + my $cnum = $self->{'cnum'}; + my $cdom = $self->{'cdom'}; + my $chome = $self->{'chome'}; # if (! defined($fn)) { # There is no filename. Look for defaults in course and global, cache @@ -1808,13 +2662,12 @@ sub readsheet { } } # $fn now has a value - $sheet->{'filename'} = $fn; + $self->{'filename'} = $fn; # see if sheet is cached my $fstring=''; if ($fstring=$spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}) { my %tmp = split(/___;___/,$fstring); - $sheet->{'f'} = \%tmp; - &setformulas($sheet); + $self->formulas(\%tmp); } else { # Not cached, need to read my %f=(); @@ -1839,73 +2692,47 @@ sub readsheet { } %f=%{&parse_sheet(\$sheetxml)}; } else { - my $sheet=''; my %tmphash = &Apache::lonnet::dump($fn,$cdom,$cnum); my ($tmp) = keys(%tmphash); - unless ($tmp =~ /^(con_lost|error|no_such_host)/i) { + if ($tmp !~ /^(con_lost|error|no_such_host)/i) { foreach (keys(%tmphash)) { $f{$_}=$tmphash{$_}; } + } else { + # Unable to grab the specified spreadsheet, + # so we get the default ones instead. + $fn = 'default_'.$stype; + $self->{'filename'} = $fn; + my $dfn = $fn; + $dfn =~ s/\_/\./g; + my $sheetxml; + if (my $fh=Apache::File->new($includedir.'/'.$dfn)) { + $sheetxml = join('',<$fh>); + } else { + $sheetxml='<field row="0" col="A">'. + '"Unable to load spreadsheet"</field>'; + } + %f=%{&parse_sheet(\$sheetxml)}; } } # Cache and set $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f); - $sheet->{'f'}=\%f; - &setformulas($sheet); - } -} - -# -------------------------------------------------------- Make new spreadsheet -sub makenewsheet { - my ($uname,$udom,$stype,$usymb)=@_; - my $sheet={}; - $sheet->{'uname'} = $uname; - $sheet->{'udom'} = $udom; - $sheet->{'sheettype'} = $stype; - $sheet->{'usymb'} = $usymb; - $sheet->{'mapid'} = $ENV{'form.mapid'}; - $sheet->{'resid'} = $ENV{'form.resid'}; - $sheet->{'cid'} = $ENV{'request.course.id'}; - $sheet->{'csec'} = $Section{$uname.':'.$udom}; - $sheet->{'coursefilename'} = $ENV{'request.course.fn'}; - $sheet->{'cnum'} = $ENV{'course.'.$ENV{'request.course.id'}.'.num'}; - $sheet->{'cdom'} = $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}; - $sheet->{'chome'} = $ENV{'course.'.$ENV{'request.course.id'}.'.home'}; - $sheet->{'coursedesc'} = $ENV{'course.'.$ENV{'request.course.id'}. - '.description'}; - $sheet->{'uhome'} = &Apache::lonnet::homeserver($uname,$udom); - # - # - $sheet->{'f'} = {}; - $sheet->{'constants'} = {}; - $sheet->{'othersheets'} = []; - $sheet->{'rowlabel'} = {}; - # - # - $sheet->{'safe'}=&initsheet($sheet->{'sheettype'}); - # - # Place all the %$sheet items into the safe space except the safe space - # itself - my $initstring = ''; - foreach (qw/uname udom sheettype usymb cid csec coursefilename - cnum cdom chome uhome/) { - $initstring.= qq{\$$_="$sheet->{$_}";}; + $self->formulas(\%f); } - $sheet->{'safe'}->reval($initstring); - return $sheet; } # ------------------------------------------------------------ Save spreadsheet sub writesheet { - my ($sheet,$makedef)=@_; - my $cid=$sheet->{'cid'}; + my $self = shift; + my ($makedef)=@_; + my $cid=$self->{'cid'}; if (&Apache::lonnet::allowed('opa',$cid)) { - my %f=&getformulas($sheet); - my $stype= $sheet->{'sheettype'}; - my $cnum = $sheet->{'cnum'}; - my $cdom = $sheet->{'cdom'}; - my $chome= $sheet->{'chome'}; - my $fn = $sheet->{'filename'}; + my %f=$self->formulas(); + my $stype= $self->{'type'}; + my $cnum = $self->{'cnum'}; + my $cdom = $self->{'cdom'}; + my $chome= $self->{'chome'}; + my $fn = $self->{'filename'}; # Cache new sheet $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f); # Write sheet @@ -1923,8 +2750,8 @@ sub writesheet { {'spreadsheet_default_'.$stype => $fn }, $cdom,$cnum); if ($reply eq 'ok' && - ($sheet->{'sheettype'} eq 'studentcalc' || - $sheet->{'sheettype'} eq 'assesscalc')) { + ($self->{'type'} eq 'studentcalc' || + $self->{'type'} eq 'assesscalc')) { # Expire the spreadsheets of the other students. &Apache::lonnet::expirespread('','','studentcalc',''); } @@ -1943,27 +2770,29 @@ sub writesheet { # "Modified workcopy" - interactive only # sub tmpwrite { - my ($sheet) = @_; + my $self = shift; my $fn=$ENV{'user.name'}.'_'. - $ENV{'user.domain'}.'_spreadsheet_'.$sheet->{'usymb'}.'_'. - $sheet->{'filename'}; + $ENV{'user.domain'}.'_spreadsheet_'.$self->{'usymb'}.'_'. + $self->{'filename'}; $fn=~s/\W/\_/g; $fn=$tmpdir.$fn.'.tmp'; my $fh; if ($fh=Apache::File->new('>'.$fn)) { - my %f = &getformulas($sheet); + my %f = $self->formulas(); while( my ($cell,$formula) = each(%f)) { print $fh &Apache::lonnet::escape($cell)."=".&Apache::lonnet::escape($formula)."\n"; } } } + # ---------------------------------------------------------- Read the temp copy sub tmpread { - my ($sheet,$nfield,$nform)=@_; + my $self = shift; + my ($nfield,$nform)=@_; my $fn=$ENV{'user.name'}.'_'. - $ENV{'user.domain'}.'_spreadsheet_'.$sheet->{'usymb'}.'_'. - $sheet->{'filename'}; + $ENV{'user.domain'}.'_spreadsheet_'.$self->{'usymb'}.'_'. + $self->{'filename'}; $fn=~s/\W/\_/g; $fn=$tmpdir.$fn.'.tmp'; my $fh; @@ -1978,116 +2807,22 @@ sub tmpread { $fo{$cell} = $formula; } } -# chomp($value); -# $fo{$name}=$value; -# if ($name=~/^A(\d+)$/) { -# if ($1>$countrows) { -# $countrows=$1; -# } -# } -# } -# } if ($nform eq 'changesheet') { $fo{'A'.$nfield}=(split(/__&&&\__/,$fo{'A'.$nfield}))[0]; unless ($ENV{'form.sel_'.$nfield} eq 'Default') { $fo{'A'.$nfield}.='__&&&__'.$ENV{'form.sel_'.$nfield}; } -# } elsif ($nfield eq 'insertrow') { -# $countrows++; -# my $newrow=substr('000000'.$countrows,-7); -# if ($nform eq 'top') { -# $fo{'A'.$countrows}='--- '.$newrow; -# } else { -# $fo{'A'.$countrows}='~~~ '.$newrow; -# } } else { if ($nfield) { $fo{$nfield}=$nform; } } - $sheet->{'f'}=\%fo; - &setformulas($sheet); -} - -################################################## -################################################## - -=pod - -=item &parmval() - -Determine the value of a parameter. - -Inputs: $what, the parameter needed, $sheet, the safe space - -Returns: The value of a parameter, or '' if none. - -This function cascades through the possible levels searching for a value for -a parameter. The levels are checked in the following order: -user, course (at section level and course level), map, and lonnet::metadata. -This function uses %parmhash, which must be tied prior to calling it. -This function also requires %courseopt and %useropt to be initialized for -this user and course. - -=cut - -################################################## -################################################## -sub parmval { - my ($what,$sheet)=@_; - my $symb = $sheet->{'usymb'}; - unless ($symb) { return ''; } - # - my $cid = $sheet->{'cid'}; - my $csec = $sheet->{'csec'}; - my $uname = $sheet->{'uname'}; - my $udom = $sheet->{'udom'}; - my $result=''; - # - my ($mapname,$id,$fn)=split(/\_\_\_/,$symb); - # Cascading lookup scheme - my $rwhat=$what; - $what =~ s/^parameter\_//; - $what =~ s/\_([^\_]+)$/\.$1/; - # - my $symbparm = $symb.'.'.$what; - my $mapparm = $mapname.'___(all).'.$what; - my $usercourseprefix = $uname.'_'.$udom.'_'.$cid; - # - my $seclevel = $usercourseprefix.'.['.$csec.'].'.$what; - my $seclevelr = $usercourseprefix.'.['.$csec.'].'.$symbparm; - my $seclevelm = $usercourseprefix.'.['.$csec.'].'.$mapparm; - # - my $courselevel = $usercourseprefix.'.'.$what; - my $courselevelr = $usercourseprefix.'.'.$symbparm; - my $courselevelm = $usercourseprefix.'.'.$mapparm; - # fourth, check user - if (defined($uname)) { - return $useropt{$courselevelr} if (defined($useropt{$courselevelr})); - return $useropt{$courselevelm} if (defined($useropt{$courselevelm})); - return $useropt{$courselevel} if (defined($useropt{$courselevel})); - } - # third, check course - if (defined($csec)) { - return $courseopt{$seclevelr} if (defined($courseopt{$seclevelr})); - return $courseopt{$seclevelm} if (defined($courseopt{$seclevelm})); - return $courseopt{$seclevel} if (defined($courseopt{$seclevel})); - } - # - return $courseopt{$courselevelr} if (defined($courseopt{$courselevelr})); - return $courseopt{$courselevelm} if (defined($courseopt{$courselevelm})); - return $courseopt{$courselevel} if (defined($courseopt{$courselevel})); - # second, check map parms - my $thisparm = $parmhash{$symbparm}; - return $thisparm if (defined($thisparm)); - # first, check default - return &Apache::lonnet::metadata($fn,$rwhat.'.default'); + $self->formulas(\%fo); } - ################################################################## ## Row label formatting routines ## ################################################################## sub format_html_rowlabel { - my $sheet = shift; + my $self = shift; my $rowlabel = shift; return '' if ($rowlabel eq ''); my ($type,$labeldata) = split(':',$rowlabel,2); @@ -2099,7 +2834,7 @@ sub format_html_rowlabel { $symb = &Apache::lonnet::unescape($symb); $title = &Apache::lonnet::unescape($title); $result = '<a href="/adm/assesscalc?usymb='.$symb. - '&uname='.$sheet->{'uname'}.'&udom='.$sheet->{'udom'}. + '&uname='.$self->{'uname'}.'&udom='.$self->{'udom'}. '&ufn='.$ufn. '&mapid='.$mapid.'&resid='.$resid.'">'.$title.'</a>'; } elsif ($type eq 'student') { @@ -2119,7 +2854,7 @@ sub format_html_rowlabel { } sub format_csv_rowlabel { - my $sheet = shift; + my $self = shift; my $rowlabel = shift; return '' if ($rowlabel eq ''); my ($type,$labeldata) = split(':',$rowlabel,2); @@ -2143,7 +2878,7 @@ sub format_csv_rowlabel { } sub format_excel_rowlabel { - my $sheet = shift; + my $self = shift; my $rowlabel = shift; return '' if ($rowlabel eq ''); my ($type,$labeldata) = split(':',$rowlabel,2); @@ -2171,11 +2906,11 @@ sub format_excel_rowlabel { # ---------------------------------------------- Update rows for course listing sub updateclasssheet { - my ($sheet) = @_; - my $cnum =$sheet->{'cnum'}; - my $cdom =$sheet->{'cdom'}; - my $cid =$sheet->{'cid'}; - my $chome =$sheet->{'chome'}; + my $self = shift; + my $cnum =$self->{'cnum'}; + my $cdom =$self->{'cdom'}; + my $cid =$self->{'cid'}; + my $chome =$self->{'chome'}; # %Section = (); # @@ -2199,62 +2934,56 @@ sub updateclasssheet { # # Find discrepancies between the course row table and this # - my %f=&getformulas($sheet); + my %f=$self->formulas(); my $changed=0; # - $sheet->{'maxrow'}=0; + $self->{'maxrow'}=0; my %existing=(); # # Now obsolete rows - foreach (keys(%f)) { - if ($_=~/^A(\d+)/) { - if ($1 > $sheet->{'maxrow'}) { - $sheet->{'maxrow'}= $1; - } - $existing{$f{$_}}=1; - unless ((defined($currentlist{$f{$_}})) || (!$1) || - ($f{$_}=~/^(~~~|---)/)) { - $f{$_}='!!! Obsolete'; - $changed=1; - } + foreach my $rownum ($self->rows()) { + my $cell = 'A'.$rownum; + if ($rownum > $self->{'maxrow'}) { + $self->{'maxrow'}= $rownum; + } + $existing{$f{$cell}}=1; + if (! defined($currentlist{$f{$cell}}) && ($f{$cell}=~/^(~~~|---)/)) { + $f{$cell}='!!! Obsolete'; + $changed=1; } } # # New and unknown keys foreach my $student (sort keys(%currentlist)) { - unless ($existing{$student}) { - $changed=1; - $sheet->{'maxrow'}++; - $f{'A'.$sheet->{'maxrow'}}=$student; - } - } - if ($changed) { - $sheet->{'f'} = \%f; - &setformulas($sheet,%f); + next if ($existing{$student}); + $changed=1; + $self->{'maxrow'}++; + $f{'A'.$self->{'maxrow'}}=$student; } + $self->formulas(\%f) if ($changed); # - &setrowlabels($sheet,\%currentlist); + $self->rowlabels(\%currentlist); } # ----------------------------------- Update rows for student and assess sheets sub get_student_rowlabels { - my ($sheet) = @_; + my $self = shift; # my %course_db; # - my $stype = $sheet->{'sheettype'}; - my $uname = $sheet->{'uname'}; - my $udom = $sheet->{'udom'}; + my $stype = $self->{'type'}; + my $uname = $self->{'uname'}; + my $udom = $self->{'udom'}; # - $sheet->{'rowlabel'} = {}; + $self->{'rowlabel'} = {}; # - my $identifier =$sheet->{'coursefilename'}.'_'.$stype; + my $identifier =$self->{'coursefilename'}.'_'.$stype; if ($rowlabel_cache{$identifier}) { - %{$sheet->{'rowlabel'}}=split(/___;___/,$rowlabel_cache{$identifier}); + %{$self->{'rowlabel'}}=split(/___;___/,$rowlabel_cache{$identifier}); } else { # Get the data and store it in the cache # Tie hash - tie(%course_db,'GDBM_File',$sheet->{'coursefilename'}.'.db', + tie(%course_db,'GDBM_File',$self->{'coursefilename'}.'.db', &GDBM_READER(),0640); if (! tied(%course_db)) { return 'Could not access course data'; @@ -2283,31 +3012,31 @@ sub get_student_rowlabels { } untie(%course_db); # Store away the data - $sheet->{'rowlabel'} = \%assesslist; - $rowlabel_cache{$identifier}=join('___;___',%{$sheet->{'rowlabel'}}); + $self->{'rowlabel'} = \%assesslist; + $rowlabel_cache{$identifier}=join('___;___',%{$self->{'rowlabel'}}); } - + } sub get_assess_rowlabels { - my ($sheet) = @_; + my $self = shift; # my %course_db; # - my $stype = $sheet->{'sheettype'}; - my $uname = $sheet->{'uname'}; - my $udom = $sheet->{'udom'}; - my $usymb = $sheet->{'usymb'}; + my $stype = $self->{'type'}; + my $uname = $self->{'uname'}; + my $udom = $self->{'udom'}; + my $usymb = $self->{'usymb'}; # - $sheet->{'rowlabel'} = {}; - my $identifier =$sheet->{'coursefilename'}.'_'.$stype.'_'.$usymb; + $self->rowlabels({}); + my $identifier =$self->{'coursefilename'}.'_'.$stype.'_'.$usymb; # if ($rowlabel_cache{$identifier}) { - %{$sheet->{'rowlabel'}}=split(/___;___/,$rowlabel_cache{$identifier}); + $self->rowlabels(split(/___;___/,$rowlabel_cache{$identifier})); } else { # Get the data and store it in the cache # Tie hash - tie(%course_db,'GDBM_File',$sheet->{'coursefilename'}.'.db', + tie(%course_db,'GDBM_File',$self->{'coursefilename'}.'.db', &GDBM_READER(),0640); if (! tied(%course_db)) { return 'Could not access course data'; @@ -2346,83 +3075,80 @@ sub get_assess_rowlabels { } untie(%course_db); # Store away the results - $sheet->{'rowlabel'} = \%parameter_labels; - $rowlabel_cache{$identifier}=join('___;___',%{$sheet->{'rowlabel'}}); + $self->rowlabels(\%parameter_labels); + $rowlabel_cache{$identifier}=join('___;___',$self->rowlabels()); } } sub updatestudentassesssheet { - my $sheet = shift; - if ($sheet->{'sheettype'} eq 'studentcalc') { - &get_student_rowlabels($sheet); + my $self = shift; + if ($self->{'type'} eq 'studentcalc') { + $self->get_student_rowlabels(); } else { - &get_assess_rowlabels($sheet); + $self->get_assess_rowlabels(); } # Determine if any of the information has changed - my %f=&getformulas($sheet); + my %f=$self->formulas(); my $changed=0; - - $sheet->{'maxrow'} = 0; + $self->{'maxrow'} = 0; my %existing=(); # Now obsolete rows - foreach my $cell (keys(%f)) { + foreach my $rownum ($self->rows()) { + my $cell = 'A'.$rownum; my $formula = $f{$cell}; - next if ($cell !~ /^A(\d+)/); - $sheet->{'maxrow'} = $1 if ($1 > $sheet->{'maxrow'}); + $self->{'maxrow'} = $rownum if ($rownum > $self->{'maxrow'}); my ($usy,$ufn)=split(/__&&&\__/,$formula); $existing{$usy}=1; - unless ((exists($sheet->{'rowlabel'}->{$usy}) && - (defined($sheet->{'rowlabel'}->{$usy})) || (!$1) || - ($formula =~ /^(~~~|---)/) )) { - $f{$_}='!!! Obsolete'; + if ( ! exists($self->{'rowlabel'}->{$usy}) || + ! defined($self->{'rowlabel'}->{$usy}) || + ($formula =~ /^(~~~|---)/) || + ($formula =~ /^\s*$/)) { + $f{$cell}='!!! Obsolete'; $changed=1; } } # New and unknown keys - foreach (keys(%{$sheet->{'rowlabel'}})) { + my %keys_hates_me = $self->rowlabels(); + foreach (keys(%keys_hates_me)) { unless ($existing{$_}) { $changed=1; - $sheet->{'maxrow'}++; - $f{'A'.$sheet->{'maxrow'}}=$_; + $self->{'maxrow'}++; + $f{'A'.$self->{'maxrow'}}=$_; } } - if ($changed) { - $sheet->{'f'} = \%f; - &setformulas($sheet); - } + $self->formulas(\%f) if ($changed); +# $self->dump_formulas_to_log(); } # ------------------------------------------------ Load data for one assessment - sub loadstudent{ - my ($sheet,$r,$c)=@_; - my %constants=(); - my %formulas=&getformulas($sheet); - $cachedassess=$sheet->{'uname'}.':'.$sheet->{'udom'}; + my $self = shift; + my ($r,$c)=@_; + my %constants = (); + my %formulas = $self->formulas(); + $cachedassess = $self->{'uname'}.':'.$self->{'udom'}; # Get ALL the student preformance data - my @tmp = &Apache::lonnet::dump($sheet->{'cid'}, - $sheet->{'udom'}, - $sheet->{'uname'}, - undef); - if ($tmp[0] !~ /^error:/) { + my @tmp = &Apache::lonnet::currentdump($self->{'cid'}, + $self->{'udom'}, + $self->{'uname'}); + if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) { %cachedstores = @tmp; } undef @tmp; # my @assessdata=(); - foreach my $cell (keys(%formulas)) { + foreach my $row ($self->rows()) { + my $cell = 'A'.$row; my $value = $formulas{$cell}; if(defined($c) && ($c->aborted())) { last; } - next if ($cell !~ /^A(\d+)/); - my $row=$1; - next if (($value =~ /^[!~-]/) || ($row==0)); + next if ($value =~ /^[!~-]/); my ($usy,$ufn)=split(/__&&&\__/,$value); - @assessdata=&exportsheet($sheet,$sheet->{'uname'}, - $sheet->{'udom'}, - 'assesscalc',$usy,$ufn,$r); + @assessdata=$self->exportsheet($self->{'uname'}, + $self->{'udom'}, + 'assesscalc',$usy,$ufn,$r); my $index=0; foreach my $col ('A','B','C','D','E','F','G','H','I','J','K','L','M', 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') { @@ -2439,24 +3165,22 @@ sub loadstudent{ } $cachedassess=''; undef %cachedstores; - $sheet->{'f'} = \%formulas; - &setformulas($sheet); - &setconstants($sheet,\%constants); + $self->formulas(\%formulas); + $self->constants(\%constants); } # --------------------------------------------------- Load data for one student # sub loadcourse { - my ($sheet,$r,$c)=@_; + my $self = shift; + my ($r,$c)=@_; # my %constants=(); - my %formulas=&getformulas($sheet); + my %formulas=$self->formulas(); # my $total=0; - foreach (keys(%formulas)) { - if ($_=~/^A(\d+)/) { - unless ($formulas{$_}=~/^[\!\~\-]/) { $total++; } - } + foreach ($self->rows()) { + $total++ if ($formulas{'A'.$_} !~ /^[!~-]/); } my $now=0; my $since=time; @@ -2466,27 +3190,27 @@ sub loadcourse { popwin.document.writeln('<html><body bgcolor="#FFFFFF">'+ '<h3>Spreadsheet Calculation Progress</h3>'+ '<form name=popremain>'+ - '<input type=text size=35 name=remaining value=Starting></form>'+ + '<input type=text size=45 name=remaining value=Starting></form>'+ '</body></html>'); popwin.document.close(); </script> ENDPOP $r->rflush(); - foreach (keys(%formulas)) { + foreach my $row ($self->rows()) { if(defined($c) && ($c->aborted())) { last; } - next if ($_!~/^A(\d+)/); - my $row=$1; - next if (($formulas{$_}=~/^[\!\~\-]/) || ($row==0)); - my ($sname,$sdom) = split(':',$formulas{$_}); - my @studentdata=&exportsheet($sheet,$sname,$sdom,'studentcalc', + my $cell = 'A'.$row; + next if ($formulas{$cell}=~/^[\!\~\-]/); + my ($sname,$sdom) = split(':',$formulas{$cell}); + my $started = time; + my @studentdata=$self->exportsheet($sname,$sdom,'studentcalc', undef,undef,$r); undef %userrdatas; $now++; $r->print('<script>popwin.document.popremain.remaining.value="'. $now.'/'.$total.': '.int((time-$since)/$now*($total-$now)). - ' secs remaining";</script>'); + ' secs remaining '.(time-$started).' last";</script>'); $r->rflush(); # my $index=0; @@ -2506,9 +3230,8 @@ ENDPOP $index++; } } - $sheet->{'f'}=\%formulas; - &setformulas($sheet); - &setconstants($sheet,\%constants); + $self->formulas(\%formulas); + $self->constants(\%constants); $r->print('<script>popwin.close()</script>'); $r->rflush(); } @@ -2516,16 +3239,18 @@ ENDPOP # ------------------------------------------------ Load data for one assessment # sub loadassessment { - my ($sheet,$r,$c)=@_; + my $self = shift; + my ($r,$c)=@_; - my $uhome = $sheet->{'uhome'}; - my $uname = $sheet->{'uname'}; - my $udom = $sheet->{'udom'}; - my $symb = $sheet->{'usymb'}; - my $cid = $sheet->{'cid'}; - my $cnum = $sheet->{'cnum'}; - my $cdom = $sheet->{'cdom'}; - my $chome = $sheet->{'chome'}; + my $uhome = $self->{'uhome'}; + my $uname = $self->{'uname'}; + my $udom = $self->{'udom'}; + my $symb = $self->{'usymb'}; + my $cid = $self->{'cid'}; + my $cnum = $self->{'cnum'}; + my $cdom = $self->{'cdom'}; + my $chome = $self->{'chome'}; + my $csec = $self->{'csec'}; my $namespace; unless ($namespace=$cid) { return ''; } @@ -2535,23 +3260,16 @@ sub loadassessment { # # get data out of the dumped stores # - my $version=$cachedstores{'version:'.$symb}; - my $scope; - for ($scope=1;$scope<=$version;$scope++) { - foreach (split(/\:/,$cachedstores{$scope.':keys:'.$symb})) { - $returnhash{$_}=$cachedstores{$scope.':'.$symb.':'.$_}; - } + if (exists($cachedstores{$symb})) { + %returnhash = %{$cachedstores{$symb}}; + } else { + %returnhash = (); } } else { # # restore individual # %returnhash = &Apache::lonnet::restore($symb,$namespace,$udom,$uname); - for (my $version=1;$version<=$returnhash{'version'};$version++) { - foreach (split(/\:/,$returnhash{$version.':keys'})) { - $returnhash{$_}=$returnhash{$version.':'.$_}; - } - } } # # returnhash now has all stores for this resource @@ -2610,66 +3328,42 @@ sub loadassessment { # my %c=(); if (tie(%parmhash,'GDBM_File', - $sheet->{'coursefilename'}.'_parms.db',&GDBM_READER(),0640)) { - my %f=&getformulas($sheet); - foreach my $cell (keys(%f)) { - next if ($cell !~ /^A/); - next if ($f{$cell} =~/^[\!\~\-]/); - if ($f{$cell}=~/^parameter/) { - if (defined($thisassess{$f{$cell}})) { - my $val = &parmval($f{$cell},$sheet); - $c{$cell} = $val; - $c{$f{$cell}} = $val; + $self->{'coursefilename'}.'_parms.db',&GDBM_READER(),0640)) { + my %f=$self->formulas(); + foreach my $row ($self->rows()) { + my $cell = 'A'.$row; + my $formula = $self->formula($cell); + next if ($formula =~/^[\!\~\-]/); + if ($formula =~ /^parameter/) { + if (defined($thisassess{$formula})) { + my $val = &parmval($formula,$symb,$uname,$udom,$csec); + $c{$cell} = $val; + $c{$formula} = $val; } } else { - my $key=$f{$cell}; - my $ckey=$key; - $key=~s/^stores\_/resource\./; - $key=~s/\_/\./g; - $c{$cell}=$returnhash{$key}; - $c{$ckey}=$returnhash{$key}; + my $ckey=$formula; + $formula=~s/^stores\_/resource\./; + $formula=~s/\_/\./g; + $c{$cell}=$returnhash{$formula}; + $c{$ckey}=$returnhash{$formula}; } } untie(%parmhash); } - &setconstants($sheet,\%c); -} - -# --------------------------------------------------------- Various form fields - -sub textfield { - my ($title,$name,$value)=@_; - return "\n<p><b>$title:</b><br>". - '<input type=text name="'.$name.'" size=80 value="'.$value.'">'; -} - -sub hiddenfield { - my ($name,$value)=@_; - return "\n".'<input type=hidden name="'.$name.'" value="'.$value.'">'; + $self->constants(\%c); } -sub selectbox { - my ($title,$name,$value,%options)=@_; - my $selout="\n<p><b>$title:</b><br>".'<select name="'.$name.'">'; - foreach (sort keys(%options)) { - $selout.='<option value="'.$_.'"'; - if ($_ eq $value) { $selout.=' selected'; } - $selout.='>'.$options{$_}.'</option>'; - } - return $selout.'</select>'; -} # =============================================== Update information in a sheet # # Add new users or assessments, etc. # - sub updatesheet { - my ($sheet)=@_; - if ($sheet->{'sheettype'} eq 'classcalc') { - return &updateclasssheet($sheet); + my $self = shift; + if ($self->{'type'} eq 'classcalc') { + return $self->updateclasssheet(); } else { - return &updatestudentassesssheet($sheet); + return $self->updatestudentassesssheet(); } } @@ -2677,54 +3371,17 @@ sub updatesheet { # # Import the data for rows # - sub loadrows { - my ($sheet,$r)=@_; + my $self = shift; + my ($r)=@_; my $c = $r->connection; - my $stype=$sheet->{'sheettype'}; - if ($stype eq 'classcalc') { - &loadcourse($sheet,$r,$c); - } elsif ($stype eq 'studentcalc') { - &loadstudent($sheet,$r,$c); - } else { - &loadassessment($sheet,$r,$c); - } -} - -# ======================================================= Forced recalculation? - -sub checkthis { - my ($keyname,$time)=@_; - if (! exists($expiredates{$keyname})) { - return 0; - } else { - return ($time<$expiredates{$keyname}); - } -} - -sub forcedrecalc { - my ($uname,$udom,$stype,$usymb)=@_; - my $key=$uname.':'.$udom.':'.$stype.':'.$usymb; - my $time=$oldsheets{$key.'.time'}; - if ($ENV{'form.forcerecalc'}) { return 1; } - unless ($time) { return 1; } - if ($stype eq 'assesscalc') { - my $map=(split(/___/,$usymb))[0]; - if (&checkthis('::assesscalc:',$time) || - &checkthis('::assesscalc:'.$map,$time) || - &checkthis('::assesscalc:'.$usymb,$time) || - &checkthis($uname.':'.$udom.':assesscalc:',$time) || - &checkthis($uname.':'.$udom.':assesscalc:'.$map,$time) || - &checkthis($uname.':'.$udom.':assesscalc:'.$usymb,$time)) { - return 1; - } + if ($self->{'type'} eq 'classcalc') { + $self->loadcourse($r,$c); + } elsif ($self->{'type'} eq 'studentcalc') { + $self->loadstudent($r,$c); } else { - if (&checkthis('::studentcalc:',$time) || - &checkthis($uname.':'.$udom.':studentcalc:',$time)) { - return 1; - } + $self->loadassessment($r,$c); } - return 0; } # ============================================================== Export handler @@ -2732,11 +3389,12 @@ sub forcedrecalc { # returns the export row for a spreadsheet. # sub exportsheet { - my ($sheet,$uname,$udom,$stype,$usymb,$fn,$r)=@_; + my $self = shift; + my ($uname,$udom,$stype,$usymb,$fn,$r)=@_; my $flag = 0; - $uname = $uname || $sheet->{'uname'}; - $udom = $udom || $sheet->{'udom'}; - $stype = $stype || $sheet->{'sheettype'}; + $uname = $uname || $self->{'uname'}; + $udom = $udom || $self->{'udom'}; + $stype = $stype || $self->{'type'}; my @exportarr=(); # This handles the assessment sheets for '_feedback', etc if (defined($usymb) && ($usymb=~/^\_(\w+)/) && @@ -2748,8 +3406,8 @@ sub exportsheet { # my $key=$uname.':'.$udom.':'.$stype.':'.$usymb; my $found=''; - if ($oldsheets{$key}) { - foreach (split(/___&\___/,$oldsheets{$key})) { + if ($Apache::lonspreadsheet::oldsheets{$key}) { + foreach (split(/___&\___/,$Apache::lonspreadsheet::oldsheets{$key})) { my ($name,$value)=split(/___=___/,$_); if ($name eq $fn) { $found=$value; @@ -2757,9 +3415,9 @@ sub exportsheet { } } unless ($found) { - &cachedssheets($sheet,$uname,$udom); - if ($oldsheets{$key}) { - foreach (split(/___&\___/,$oldsheets{$key})) { + &cachedssheets($self,$uname,$udom); + if ($Apache::lonspreadsheet::oldsheets{$key}) { + foreach (split(/___&\___/,$Apache::lonspreadsheet::oldsheets{$key})) { my ($name,$value)=split(/___=___/,$_); if ($name eq $fn) { $found=$value; @@ -2785,12 +3443,13 @@ sub exportsheet { # # Not cached # - my ($newsheet)=&makenewsheet($uname,$udom,$stype,$usymb); - &readsheet($newsheet,$fn); - &updatesheet($newsheet); - &loadrows($newsheet,$r); - &calcsheet($newsheet); - @exportarr=&exportdata($newsheet); + my $newsheet = Apache::lonspreadsheet::Spreadsheet->new($uname,$udom, + $stype,$usymb); + $newsheet->readsheet($fn); + $newsheet->updatesheet(); + $newsheet->loadrows($r); + $newsheet->calcsheet(); + @exportarr=$newsheet->exportdata(); ## ## Store now ## @@ -2801,7 +3460,7 @@ sub exportsheet { if ($stype eq 'studentcalc') { my @tmp = &Apache::lonnet::get('nohist_calculatedsheets', [$key], - $sheet->{'cdom'},$sheet->{'cnum'}); + $self->{'cdom'},$self->{'cnum'}); if ($tmp[0]!~/^error/) { # We only got one key, so we will access it directly. foreach (split('___&___',$tmp[1])) { @@ -2812,8 +3471,8 @@ sub exportsheet { } } else { my @tmp = &Apache::lonnet::get('nohist_calculatedsheets_'. - $sheet->{'cid'},[$key], - $sheet->{'udom'},$sheet->{'uname'}); + $self->{'cid'},[$key], + $self->{'udom'},$self->{'uname'}); if ($tmp[0]!~/^error/) { # We only got one key, so we will access it directly. foreach (split('___&___',$tmp[1])) { @@ -2844,469 +3503,20 @@ sub exportsheet { my $result = &Apache::lonnet::put('nohist_calculatedsheets', { $key => $newstore, $timekey => $now }, - $sheet->{'cdom'}, - $sheet->{'cnum'}); + $self->{'cdom'}, + $self->{'cnum'}); } else { - my $result = &Apache::lonnet::put('nohist_calculatedsheets_'.$sheet->{'cid'}, + my $result = &Apache::lonnet::put('nohist_calculatedsheets_'.$self->{'cid'}, { $key => $newstore, $timekey => $now }, - $sheet->{'udom'}, - $sheet->{'uname'}); + $self->{'udom'}, + $self->{'uname'}); } return @exportarr; } -# ============================================================ Expiration Dates -# -# Load previously cached student spreadsheets for this course -# -sub load_spreadsheet_expirationdates { - undef %expiredates; - my $cid=$ENV{'request.course.id'}; - my @tmp = &Apache::lonnet::dump('nohist_expirationdates', - $ENV{'course.'.$cid.'.domain'}, - $ENV{'course.'.$cid.'.num'}); - if (lc($tmp[0]) !~ /^error/){ - %expiredates = @tmp; - } -} - -# ===================================================== Calculated sheets cache -# -# Load previously cached student spreadsheets for this course -# - -sub cachedcsheets { - my $cid=$ENV{'request.course.id'}; - my @tmp = &Apache::lonnet::dump('nohist_calculatedsheets', - $ENV{'course.'.$cid.'.domain'}, - $ENV{'course.'.$cid.'.num'}); - if ($tmp[0] !~ /^error/) { - my %StupidTempHash = @tmp; - while (my ($key,$value) = each %StupidTempHash) { - $oldsheets{$key} = $value; - } - } -} - -# ===================================================== Calculated sheets cache -# -# Load previously cached assessment spreadsheets for this student -# - -sub cachedssheets { - my ($sheet,$uname,$udom) = @_; - $uname = $uname || $sheet->{'uname'}; - $udom = $udom || $sheet->{'udom'}; - if (! $loadedcaches{$uname.'_'.$udom}) { - my @tmp = &Apache::lonnet::dump('nohist_calculatedsheets', - $sheet->{'udom'}, - $sheet->{'uname'}); - if ($tmp[0] !~ /^error/) { - my %TempHash = @tmp; - my $count = 0; - while (my ($key,$value) = each %TempHash) { - $oldsheets{$key} = $value; - $count++; - } - $loadedcaches{$sheet->{'uname'}.'_'.$sheet->{'udom'}}=1; - } - } - -} - -# ===================================================== Calculated sheets cache -# -# Load previously cached assessment spreadsheets for this student -# - -# ================================================================ Main handler -# -# Interactive call to screen -# -# -sub handler { - my $r=shift; - - my ($sheettype) = ($r->uri=~/\/(\w+)$/); - - if (! exists($ENV{'form.Status'})) { - $ENV{'form.Status'} = 'Active'; - } - if ( ! exists($ENV{'form.output'}) || - ($sheettype ne 'classcalc' && - lc($ENV{'form.output'}) eq 'recursive excel')) { - $ENV{'form.output'} = 'HTML'; - } - # - # Overload checking - # - # Check this server - my $loaderror=&Apache::lonnet::overloaderror($r); - if ($loaderror) { return $loaderror; } - # Check the course homeserver - $loaderror= &Apache::lonnet::overloaderror($r, - $ENV{'course.'.$ENV{'request.course.id'}.'.home'}); - if ($loaderror) { return $loaderror; } - # - # HTML Header - # - if ($r->header_only) { - $r->content_type('text/html'); - $r->send_http_header; - return OK; - } - # - # Global directory configs - # - $includedir = $r->dir_config('lonIncludes'); - $tmpdir = $r->dir_config('lonDaemons').'/tmp/'; - # - # Roles Checking - # - # Needs to be in a course - if (! $ENV{'request.course.fn'}) { - # Not in a course, or not allowed to modify parms - $ENV{'user.error.msg'}= - $r->uri.":opa:0:0:Cannot modify spreadsheet"; - return HTTP_NOT_ACCEPTABLE; - } - # - # Get query string for limited number of parameters - # - &Apache::loncommon::get_unprocessed_cgi - ($ENV{'QUERY_STRING'},['uname','udom','usymb','ufn','mapid','resid']); - # - # Deal with restricted student permissions - # - if ($ENV{'request.role'} =~ /^st\./) { - delete $ENV{'form.unewfield'} if (exists($ENV{'form.unewfield'})); - delete $ENV{'form.unewformula'} if (exists($ENV{'form.unewformula'})); - } - # - # Look for special assessment spreadsheets - '_feedback', etc. - # - if (($ENV{'form.usymb'}=~/^\_(\w+)/) && (!$ENV{'form.ufn'} || - $ENV{'form.ufn'} eq '' || - $ENV{'form.ufn'} eq 'default')) { - $ENV{'form.ufn'}='default_'.$1; - } - if (!$ENV{'form.ufn'} || $ENV{'form.ufn'} eq 'default') { - $ENV{'form.ufn'}='course_default_'.$sheettype; - } - # - # Interactive loading of specific sheet? - # - if (($ENV{'form.load'}) && ($ENV{'form.loadthissheet'} ne 'Default')) { - $ENV{'form.ufn'}=$ENV{'form.loadthissheet'}; - } - # - # Determine the user name and domain for the sheet. - my $aname; - my $adom; - unless ($ENV{'form.uname'}) { - $aname=$ENV{'user.name'}; - $adom=$ENV{'user.domain'}; - } else { - $aname=$ENV{'form.uname'}; - $adom=$ENV{'form.udom'}; - } - # - # Open page, try to prevent browser cache. - # - $r->content_type('text/html'); - $r->header_out('Cache-control','no-cache'); - $r->header_out('Pragma','no-cache'); - $r->send_http_header; - # - # Header.... - # - $r->print('<html><head><title>LON-CAPA Spreadsheet</title>'); - my $nothing = "''"; - if ($ENV{'browser.type'} eq 'explorer') { - $nothing = "'javascript:void(0);'"; - } - - if ($ENV{'request.role'} !~ /^st\./) { - $r->print(<<ENDSCRIPT); -<script language="JavaScript"> - - var editwin; - - function celledit(cellname,cellformula) { - var edit_text = ''; - // cellformula may contain less-than and greater-than symbols, so - // we need to escape them? - edit_text +='<html><head><title>Cell Edit Window</title></head><body>'; - edit_text += '<form name="editwinform">'; - edit_text += '<center><h3>Cell '+cellname+'</h3>'; - edit_text += '<textarea name="newformula" cols="40" rows="6"'; - edit_text += ' wrap="off" >'+cellformula+'</textarea>'; - edit_text += '</br>'; - edit_text += '<input type="button" name="accept" value="Accept"'; - edit_text += ' onClick=\\\'javascript:'; - edit_text += 'opener.document.sheet.unewfield.value='; - edit_text += '"'+cellname+'";'; - edit_text += 'opener.document.sheet.unewformula.value='; - edit_text += 'document.editwinform.newformula.value;'; - edit_text += 'opener.document.sheet.submit();'; - edit_text += 'self.close()\\\' />'; - edit_text += ' '; - edit_text += '<input type="button" name="abort" '; - edit_text += 'value="Discard Changes"'; - edit_text += ' onClick="javascript:self.close()" />'; - edit_text += '</center></body></html>'; - - if (editwin != null && !(editwin.closed) ) { - editwin.close(); - } - - editwin = window.open($nothing,'CellEditWin','height=200,width=350,scrollbars=no,resizeable=yes,alwaysRaised=yes,dependent=yes',true); - editwin.document.write(edit_text); - } - - function changesheet(cn) { - document.sheet.unewfield.value=cn; - document.sheet.unewformula.value='changesheet'; - document.sheet.submit(); - } - - function insertrow(cn) { - document.sheet.unewfield.value='insertrow'; - document.sheet.unewformula.value=cn; - document.sheet.submit(); - } - -</script> -ENDSCRIPT - } - $r->print('</head>'.&Apache::loncommon::bodytag('Grades Spreadsheet'). - '<form action="'.$r->uri.'" name="sheet" method="post">'); - $r->print(&hiddenfield('uname',$ENV{'form.uname'}). - &hiddenfield('udom',$ENV{'form.udom'}). - &hiddenfield('usymb',$ENV{'form.usymb'}). - &hiddenfield('unewfield',''). - &hiddenfield('unewformula','')); - $r->rflush(); - # - # Full recalc? - # - if ($ENV{'form.forcerecalc'}) { - $r->print('<h4>Completely Recalculating Sheet ...</h4>'); - undef %spreadsheets; - undef %courserdatas; - undef %userrdatas; - undef %defaultsheets; - undef %rowlabel_cache; - } - # Read new sheet or modified worksheet - my ($sheet)=&makenewsheet($aname,$adom,$sheettype,$ENV{'form.usymb'}); - # - # Check user permissions - if (($sheet->{'sheettype'} eq 'classcalc' ) || - ($sheet->{'uname'} ne $ENV{'user.name'} ) || - ($sheet->{'udom'} ne $ENV{'user.domain'})) { - unless (&Apache::lonnet::allowed('vgr',$sheet->{'cid'})) { - $r->print('<h1>Access Permission Denied</h1>'. - '</form></body></html>'); - return OK; - } - } - # Print out user information - $r->print('<h2>'.$sheet->{'coursedesc'}.'</h2>'); - if ($sheet->{'sheettype'} ne 'classcalc') { - $r->print('<h2>'.&gettitle($sheet).'</h2><p>'); - } - if ($sheet->{'sheettype'} eq 'assesscalc') { - $r->print('<b>User:</b> '.$sheet->{'uname'}. - '<br /><b>Domain:</b> '.$sheet->{'udom'}.'<br />'); - } - if ($sheet->{'sheettype'} eq 'studentcalc' || - $sheet->{'sheettype'} eq 'assesscalc') { - $r->print('<b>Section/Group:</b>'.$sheet->{'csec'}.'</p>'); - } - # - # If a new formula had been entered, go from work copy - if ($ENV{'form.unewfield'}) { - $r->print('<h2>Modified Workcopy</h2>'); - $ENV{'form.unewformula'}=~s/\'/\"/g; - $r->print('<p>Cell '.$ENV{'form.unewfield'}.' = <pre>'); - $r->print(&HTML::Entities::encode($ENV{'form.unewformula'}). - '</pre></p>'); - $sheet->{'filename'} = $ENV{'form.ufn'}; - &tmpread($sheet,$ENV{'form.unewfield'},$ENV{'form.unewformula'}); - } elsif ($ENV{'form.saveas'}) { - $sheet->{'filename'} = $ENV{'form.ufn'}; - &tmpread($sheet); - } else { - &readsheet($sheet,$ENV{'form.ufn'}); - } - # Additional options - if ($sheet->{'sheettype'} eq 'assesscalc') { - $r->print('<p><font size=+2>'. - '<a href="/adm/studentcalc?'. - 'uname='.$sheet->{'uname'}. - '&udom='.$sheet->{'udom'}.'">'. - 'Level up: Student Sheet</a></font></p>'); - } - if (($sheet->{'sheettype'} eq 'studentcalc') && - (&Apache::lonnet::allowed('vgr',$sheet->{'cid'}))) { - $r->print ('<p><font size=+2><a href="/adm/classcalc">'. - 'Level up: Course Sheet</a></font></p>'); - } - # Recalc button - $r->print('<br />'. - '<input type="submit" name="forcerecalc" '. - 'value="Completely Recalculate Sheet"></p>'); - # Save dialog - if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) { - my $fname=$ENV{'form.ufn'}; - $fname=~s/\_[^\_]+$//; - if ($fname eq 'default') { $fname='course_default'; } - $r->print('<input type=submit name=saveas value="Save as ...">'. - '<input type=text size=20 name=newfn value="'.$fname.'">'. - 'make default: <input type=checkbox name="makedefufn"><p>'); - } - $r->print(&hiddenfield('ufn',$sheet->{'filename'})); - # Load dialog - if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) { - $r->print('<p><input type=submit name=load value="Load ...">'. - '<select name="loadthissheet">'. - '<option name="default">Default</option>'); - foreach (&othersheets($sheet)) { - $r->print('<option name="'.$_.'"'); - if ($ENV{'form.ufn'} eq $_) { - $r->print(' selected'); - } - $r->print('>'.$_.'</option>'); - } - $r->print('</select><p>'); - if ($sheet->{'sheettype'} eq 'studentcalc') { - &setothersheets($sheet, - &othersheets($sheet,'assesscalc')); - } - } - # - # Set up caching mechanisms - # - &load_spreadsheet_expirationdates(); - # Clear out old caches if we have not seen this class before. - if (exists($oldsheets{'course'}) && - $oldsheets{'course'} ne $sheet->{'cid'}) { - undef %oldsheets; - undef %loadedcaches; - } - $oldsheets{'course'} = $sheet->{'cid'}; - # - if ($sheet->{'sheettype'} eq 'classcalc') { - $r->print("Loading previously calculated student sheets ...\n"); - $r->rflush(); - &cachedcsheets(); - } elsif ($sheet->{'sheettype'} eq 'studentcalc') { - $r->print("Loading previously calculated assessment sheets ...\n"); - $r->rflush(); - &cachedssheets($sheet); - } - # Update sheet, load rows - $r->print("Loaded sheet(s), updating rows ...<br>\n"); - $r->rflush(); - # - &updatesheet($sheet); - $r->print("Updated rows, loading row data ...\n"); - $r->rflush(); - # - &loadrows($sheet,$r); - $r->print("Loaded row data, calculating sheet ...<br>\n"); - $r->rflush(); - # - my $calcoutput=&calcsheet($sheet); - $r->print('<h3><font color=red>'.$calcoutput.'</h3></font>'); - # See if something to save - if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) { - my $fname=''; - if ($ENV{'form.saveas'} && ($fname=$ENV{'form.newfn'})) { - $fname=~s/\W/\_/g; - if ($fname eq 'default') { $fname='course_default'; } - $fname.='_'.$sheet->{'sheettype'}; - $sheet->{'filename'} = $fname; - $ENV{'form.ufn'}=$fname; - $r->print('<p>Saving spreadsheet: '. - &writesheet($sheet,$ENV{'form.makedefufn'}). - '<p>'); - } - } - # - # Write the modified worksheet - $r->print('<b>Current sheet:</b> '.$sheet->{'filename'}.'</p>'); - &tmpwrite($sheet); - if ($sheet->{'sheettype'} eq 'assesscalc') { - $r->print('<p>Show rows with empty A column: '); - } else { - $r->print('<p>Show empty rows: '); - } - # - $r->print(&hiddenfield('userselhidden','true'). - '<input type="checkbox" name="showall" onClick="submit()"'); - # - if ($ENV{'form.showall'}) { - $r->print(' checked'); - } else { - unless ($ENV{'form.userselhidden'}) { - unless - ($ENV{'course.'.$sheet->{'cid'}.'.hideemptyrows'} eq 'yes') { - $r->print(' checked'); - $ENV{'form.showall'}=1; - } - } - } - $r->print('>'); - # - # CSV format checkbox (classcalc sheets only) - $r->print(' Output as <select name="output" size="1" onClick="submit()">'. - "\n"); - foreach my $mode (qw/HTML CSV Excel/) { - $r->print('<option value="'.$mode.'"'); - if ($ENV{'form.output'} eq $mode) { - $r->print(' selected '); - } - $r->print('>'.$mode.'</option>'."\n"); - } -# -# Mulit-sheet excel takes too long and does not work at all for large -# classes. Future inclusion of this option may be possible with the -# Spreadsheet::WriteExcel::Big and speed improvements. -# -# if ($sheet->{'sheettype'} eq 'classcalc') { -# $r->print('<option value="recursive excel"'); -# if ($ENV{'form.output'} eq 'recursive excel') { -# $r->print(' selected '); -# } -# $r->print(">Multi-Sheet Excel</option>\n"); -# } - $r->print("</select>\n"); - # - if ($sheet->{'sheettype'} eq 'classcalc') { - $r->print(' Student Status: '. - &Apache::lonhtmlcommon::StatusOptions - ($ENV{'form.Status'},'sheet')); - } - # - # Buttons to insert rows -# $r->print(<<ENDINSERTBUTTONS); -#<br> -#<input type='button' onClick='insertrow("top");' -#value='Insert Row Top'> -#<input type='button' onClick='insertrow("bottom");' -#value='Insert Row Bottom'><br> -#ENDINSERTBUTTONS - # Print out sheet - &outsheet($sheet,$r); - $r->print('</form></body></html>'); - # Done - return OK; -} - 1; + __END__