');
+ if (! scalar(@Problems) || ! defined($Problems[0])) {
+ $r->print(&mt('resource is undefined'));
+ } elsif (!$show_named && @Students < $threshold) {
+ $r->print(&mt('The number of students matching the selection criteria is too few for display of submission data for anonymous surveys.').' '.&mt('There must be at least [quant,_1,student].',$threshold).' '.&mt('Contact a Domain Coordinator if you need the threshold to be changed for this course.'));
} else {
- my $resource = $current_problem->{'resource'};
- $r->print('
'.$resource->{'title'}.'
');
- $r->print('
'.$resource->{'src'}.'
');
- $r->print(&Apache::lonstathelpers::render_resource($resource));
- $r->rflush();
- my %Data = &Apache::lonstathelpers::get_problem_data
- ($resource->{'src'});
- my $ProblemData = $Data{$current_problem->{'part'}.
- '.'.
- $current_problem->{'respid'}};
- &prepare_excel_output($r,$current_problem,
- $ProblemData,\@Students);
+ if (scalar(@Problems) == 1) {
+ my $resource = $Problems[0];
+ $r->print('
'.
- &mt('Preparing Excel spreadsheet of student responses').
- '
'.
- '
'.
- &mt('See the status bar above for student answer computation progress').
- '
');
#
- &Apache::lonstathelpers::GetStudentAnswers($r,$problem,$Students,
- 'Statistics','stats_status');
+ # Set a flag for the case when there is just one problem
+ my $single_response = 0;
+ if (scalar(@$problems) == 1 &&
+ $problems->[0]->countResponses == 1) {
+ $single_response = 1;
+ }
#
- $r->print('');
- $r->rflush();
- my @Columns = ( 'username','domain','attempt','time',
- 'submission','correct', 'grading','awarded','weight',
- 'score');
- my $awarded_col = 7;
- my $weight_col = 8;
+ # Compute the number of columns per response
+ my @extra_resp_headers = &get_extra_response_headers($show_named);
#
- # Create excel worksheet
- my $filename = '/prtspool/'.
- $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
- time.'_'.rand(1000000000).'.xls';
- my $workbook = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);
- if (! defined($workbook)) {
- $r->log_error("Error creating excel spreadsheet $filename: $!");
- $r->print('
'.&mt("Unable to create new Excel file. ".
- "This error has been logged. ".
- "Please alert your LON-CAPA administrator").
- '
');
- return undef;
+ # Create the table header
+ my @student_columns = &get_student_columns($show_named);
+ my %headers;
+ my $student_column_count = scalar(@student_columns);
+ $headers{'problem'} = qq{
\
};
+ foreach my $field (@student_columns) {
+ $headers{'student'}.= '
'.&mt(ucfirst($field)).'
';
}
#
- $workbook->set_tempdir('/home/httpd/perl/tmp');
+ # we put the headers into the %headers hash
+ my $total_col = $student_column_count;
+ my $nonempty_part_headers = 0;
+ #
+ my %problem_analysis;
+ foreach my $prob (@$problems) {
+ my %analysis = &Apache::lonstathelpers::get_problem_data($prob->src);
+ $problem_analysis{$prob->src}=\%analysis;
+ my $symb = $prob->symb();
+ #
+ my $prob_span = 0;
+ my $single_part = 0;
+ if (scalar(@{$prob->parts}) == 1) {
+ $single_part = 1;
+ }
+ my $shown_parts = 0;
+ foreach my $partid (@{$prob->parts}) {
+ if (($prob->is_anonsurvey($partid)) || ($anoncounter->{$symb."\0".$partid})) {
+ next if ($show_named);
+ } else {
+ next unless ($show_named);
+ }
+ $shown_parts ++;
+ my $part_span = 0;
+ my $responses = [$prob->responseIds($partid)];
+ my $resptypes = [$prob->responseType($partid)];
+ for (my $i=0;$i[$i];
+ my @headers = &get_headers($prob,$partid,$respid,
+ $resptypes->[$i],
+ $problem_analysis{$prob->src},
+ 'html','display',
+ @extra_resp_headers);
+ if (scalar(@headers)>0) {
+ $total_col += scalar(@headers);
+ $part_span += scalar(@headers);
+ $headers{'response'} .=
+ '
';
+ }
+ #
+ # Figure out what it is we need to output for this student
+ my @essays;
+ my %prob_data;
+ my $maxrow;
+ foreach my $prob (@$problems) {
+ my $symb = $prob->symb;
+ $prob_data{$symb}={};
+ foreach my $partid (@{$prob->parts}) {
+ if (($prob->is_anonsurvey($partid)) || ($anoncounter->{$symb."\0".$partid})) {
+ next if ($show_named);
+ } else {
+ next unless ($show_named);
+ }
+ my @responses = $prob->responseIds($partid);
+ my @response_type = $prob->responseType($partid);
+ for (my $i=0;$i<=$#responses;$i++) {
+ my $respid = $responses[$i];
+ my $results =
+ &Apache::loncoursedata::get_response_data_by_student
+ ($student,$prob->symb(),$respid);
+ my $resptype = $response_type[$i];
+ my @headers = &get_headers($prob,$partid,$respid,
+ $resptype,
+ $problem_analysis{$prob->src},
+ 'html','normal',
+ @extra_resp_headers);
+ my $width = scalar(@headers);
+ next if ($width < 1);
+ my $resp_data;
+ $resp_data->{'fake'} = qq{
};
+ if (! defined($results)) {
+ $results = [];
+ }
+ #
+ if (scalar(@$results) > $maxrow && $resptype ne 'essay') {
+ $maxrow = scalar(@$results);
+ }
+ for (my $j=scalar(@$results)-1;$j>=0;$j--) {
+ if ($env{'form.all_sub'} ne 'true') {
+ next if ($j ne scalar(@$results)-1);
+ }
+ my $response = &hashify_response($results->[$j],
+ $prob,
+ $student,
+ $partid,
+ $respid);
+ if ($resptype eq 'essay') {
+ push(@essays,
+ &html_essay_results(\@headers,
+ $prob,$partid,$respid,
+ $response,
+ $single_response).
+ '');
+ } elsif (lc($resptype) eq 'task') {
+ my $results =
+ &html_task_results(\@headers,
+ $prob,$partid,$respid,
+ $response,$resptype);
+ if ($results) {
+ push(@{$resp_data->{'real'}},$results);
+ }
+ } else {
+ push(@{$resp_data->{'real'}},
+ &html_non_essay_results(\@headers,
+ $prob,$partid,$respid,
+ $response,$resptype));
+ }
+ }
+ $prob_data{$prob->symb}->{$partid}->{$respid}=$resp_data;
+ } # end of $i loop
+ } # end of partid loop
+ } # end of prob loop
+ #
+ # if there is no data, skip this student.
+ next if (! $maxrow && ! scalar(@essays));
+ #
+ # Go through the problem data and output a row.
+ my $printed_something;
+ for (my $rows_output = 0;$rows_output<$maxrow;$rows_output++) {
+ my $html;
+ my $no_data = 1;
+ foreach my $prob (@$problems) {
+ foreach my $partid (@{$prob->parts}) {
+ my @responses = $prob->responseIds($partid);
+ my @response_type = $prob->responseType($partid);
+ for (my $i=0;$i<=$#responses;$i++) {
+ my $respid = $responses[$i];
+ my $resp_data =
+ $prob_data{$prob->symb}->{$partid}->{$respid};
+ next if ($response_type[$i] eq 'essay');
+ if (defined($resp_data->{'real'}->[$rows_output])) {
+ $html .= $resp_data->{'real'}->[$rows_output];
+ $no_data = 0;
+ } else {
+ $html .= $resp_data->{'fake'};
+ }
+ }
+ }
+ }
+ if (! $no_data) {
+ $r->print(&Apache::loncommon::start_data_table_row().$student_row_data.$html.
+ &Apache::loncommon::end_data_table_row().$/);
+ $printed_something=1;
+ }
+ }
+ if (@essays) {
+ my $tr = &Apache::loncommon::start_data_table_row();
+ my $td = qq{
'.$/.$tr.$td,@essays).''.$/);
+ undef(@essays);
+ }
+ } # end of student loop
+ $r->print(&Apache::loncommon::end_data_table().$/);
+ return;
+}
+
+sub hashify_response {
+ my ($response,$prob,$student,$partid,$respid) =@_;
+ my $resp_hash = {};
+ if ($env{'form.correctans'} eq 'true') {
+ $resp_hash->{'Correct'} =
+ &Apache::lonstathelpers::get_student_answer
+ ($prob,$student->{'username'},$student->{'domain'},
+ $partid,$respid);
+ }
+ $resp_hash->{'Submission'} =
+ $response->[&Apache::loncoursedata::RDs_submission()];
+ $resp_hash->{'Time'} =
+ $response->[&Apache::loncoursedata::RDs_timestamp()];
+ $resp_hash->{'Attempt'} =
+ $response->[&Apache::loncoursedata::RDs_tries()];
+ $resp_hash->{'Awarded'} =
+ $response->[&Apache::loncoursedata::RDs_awarded()];
+ if ($prob->is_task()) {
+ $resp_hash->{'Grader'} =
+ $response->[&Apache::loncoursedata::RDs_response_eval_2()];
+ if ($resp_hash->{'Attempt'} eq '0') {
+ $resp_hash->{'Attempt'} = '';
+ }
+ $resp_hash->{'Award Detail'} =
+ $response->[&Apache::loncoursedata::RDs_part_award()];
+ $resp_hash->{'Status'} =
+ $response->[&Apache::loncoursedata::RDs_response_eval()];
+ } else {
+ $resp_hash->{'Award Detail'} =
+ $response->[&Apache::loncoursedata::RDs_awarddetail()];
+ }
+
+ return $resp_hash;
+}
+
+#####################################################
+##
+## HTML helper routines
+##
+#####################################################
+sub html_essay_results {
+ my ($headers,$prob,$partid,$respid,$response,$single_response)=@_;
+ if (! ref($headers) || ref($headers) ne 'ARRAY') {
+ return '';
+ }
+ # Start of telling them what problem, part, and response
+ my $Str;
+ if (! $single_response) {
+ my $id = $prob->compTitle;
+ if (defined($partid) && $partid ne '0') {
+ $id .= ' '.$prob->part_display($partid);
+ }
+ if (defined($respid)) {
+ $id .= ' '.$respid;
+ }
+ $Str .= ''.$id.''.(' 'x4);
+ }
#
- my $format = &Apache::loncommon::define_excel_formats($workbook);
- my $worksheet = $workbook->addworksheet('Student Submission Data');
+ shift(@$headers); # Get rid of the Submission header
+ my $correct = '';
+ if ($headers->[0] eq 'Correct') {
+ $correct = &html_format_essay_sub($response->{'Correct'});
+ shift(@$headers);
+ }
+ $Str .= ''.
+ join('',
+ map {
+ (' 'x4).&mt($_.': [_1]',$response->{$_});
+ } @$headers).'';
+ if (@$headers || ! $single_response) {
+ $Str .= ' ';
+ }
+ $Str .= &html_format_essay_sub($response->{'Submission'});
#
- # Make sure we get new weight data instead of data on a 10 minute delay
- &Apache::lonnet::clear_EXT_cache_status();
+ if (defined($correct) && $correct !~ /^\s*$/) {
+ $Str .= ''.&mt('Correct').''.$correct
+ }
+ return $Str;
+}
+
+sub html_format_essay_sub {
+ my ($submission) = @_;
+ return '' if (! defined($submission) || $submission eq '');
+ $submission = &HTML::Entities::decode($submission);
+ $submission =~ s/\\\"/\"/g;
+ $submission =~ s/\\\'/\'/g;
+ $submission =~ s|\\r\\n|$/|g;
+ $submission = &HTML::Entities::encode($submission,'<>&"');
+ $submission =~ s|$/\s*$/|$/
$/|g;
+ $submission =~ s|\\||g;
+ $submission = '
'.$submission.'
';
+ return $submission;
+}
+
+sub html_task_results {
+ my ($headers,$prob,$partid,$respid,$response,$resptype) = @_;
+ if (! ref($headers) || ref($headers) ne 'ARRAY' || ! scalar(@$headers)) {
+ return '';
+ }
+
+ my @values;
+ @values = map { $response->{$_}; } @$headers;
+
+ my $td = '
';
+ my $str = $td.join('
'.$td,@values).'';
+ return $str;
+}
+
+sub html_non_essay_results {
+ my ($headers,$prob,$partid,$respid,$response,$resptype) = @_;
+ if (! ref($headers) || ref($headers) ne 'ARRAY' || ! scalar(@$headers)) {
+ return '';
+ }
+ #
+ my $submission = &HTML::Entities::decode(&unescape($response->{'Submission'}));
+ return '' if (! defined($submission) || $submission eq '');
+ $submission =~ s/\\\"/\"/g;
+ $submission =~ s/\\\'/\'/g;
+ if ($resptype eq 'radiobutton') {
+ $submission = &HTML::Entities::encode($submission,'<>&"');
+ $submission =~ s/=([^=])$//;
+ $submission = ''.$submission.'';
+ }
+ $response->{'Submission'} = $submission;
#
- # Put on the standard headers and whatnot
- my $rows_output=0;
- $worksheet->write($rows_output++,0,$resource->{'title'},$format->{'h1'});
- $worksheet->write($rows_output++,0,$resource->{'src'},$format->{'h3'});
+ my @values;
+ if ($resptype =~ /^(option|match|rank)$/) {
+ my %submission =
+ map {
+ my ($foil,$value) = split('=',&unescape($_));
+ ($foil,$value);
+ } split('&',$response->{'Submission'});
+ my %correct;
+ if (exists($response->{'Correct'})) {
+ %correct =
+ map {
+ my ($foil,$value)=split('=',&unescape($_));
+ ($foil,$value);
+ } split('&',$response->{'Correct'});
+ }
+ #
+ foreach my $original_header (@$headers) {
+ if ($original_header =~ /^_/) {
+ # '_' denotes a foil column
+ my ($header) = ($original_header =~ m/^_(.*)$/);
+ my $option = '';
+ if ( my ($foil) = ($header =~ /(.*) Correct$/)) {
+ if (exists($correct{$foil})) {
+ $option = $correct{$foil};
+ }
+ } elsif (exists($submission{$header})) {
+ $option = $submission{$header};
+ }
+ push(@values,&HTML::Entities::encode($option));
+ } elsif ($original_header eq 'Time') {
+ push(@values,&Apache::lonlocal::locallocaltime($response->{$original_header}));
+ } else {
+ # A normal column
+ push(@values,$response->{$original_header});
+ }
+ }
+ } else {
+ foreach my $original_header (@$headers) {
+ if ($original_header eq 'Time') {
+ push(@values,&Apache::lonlocal::locallocaltime($response->{$original_header}));
+ } else {
+ # A normal column
+ push(@values,$response->{$original_header});
+ }
+ }
+ }
+ my $td = '
';
+ my $str = $td.join('
'.$td,@values).'';
+ return $str;
+}
+
+
+#########################################################
+#########################################################
+##
+## Excel Output Routines
+##
+#########################################################
+#########################################################
+sub prepare_excel_output {
+ my ($r,$Problems,$Students,$anoncounter,$show_named) = @_;
+ my $c = $r->connection();
+ #
+ #
+ # Determine the number of columns in the spreadsheet
+ my $columncount = 3; # username, domain, id
+ my @extra_resp_headers = &get_extra_response_headers($show_named);
+ my $lastprob;
+ my %problem_analysis;
+ foreach my $prob (@$Problems) {
+ my $symb = $prob->symb();
+ my %analysis = &Apache::lonstathelpers::get_problem_data($prob->src);
+ $problem_analysis{$prob->src}=\%analysis;
+ foreach my $partid (@{$prob->parts}) {
+ if (($prob->is_anonsurvey($partid)) || ($anoncounter->{$symb."\0".$partid})) {
+ next if ($show_named);
+ } else {
+ next unless ($show_named);
+ }
+
+ my $responses = [$prob->responseIds($partid)];
+ my $resptypes = [$prob->responseType($partid)];
+ for (my $i=0;$i[$i],
+ $resptypes->[$i],
+ $problem_analysis{$prob->src},
+ 'excel','display',
+ @extra_resp_headers);
+ $columncount += scalar(@headers);
+ }
+ }
+ last if ($columncount > 255);
+ $lastprob = $prob;
+ }
+ if ($columncount > 255) {
+ $r->print('
'.&mt('Unable to complete request').'
'.$/.
+ '
'.&mt('LON-CAPA is unable to produce your Excel spreadsheet because your selections will result in more than 255 columns. Excel allows only 255 columns in a spreadsheet.').'
'.$/.
+ '
'.&mt('Consider selecting fewer problems to generate reports on, or reducing the number of items per problem. Or use HTML or CSV output.').'
'.$/);
+ if (ref($lastprob)) {
+ $r->print('
'.&mt('The last problem that will fit in the current spreadsheet is [_1].',$lastprob->compTitle).'
');
+ }
+ $r->rflush();
+ return;
+ }
+ #
+ # Print out a message telling them what we are doing
+ if (scalar(@$Problems) > 1) {
+ $r->print('
'.
+ &mt('Preparing Excel spreadsheet of student responses to [_1] problems',
+ scalar(@$Problems)).
+ '
');
+ } else {
+ $r->print('
'.
+ &mt('Preparing Excel spreadsheet of student responses').
+ '
');
+ }
+ $r->rflush();
+ #
+ # Create the excel spreadsheet
+ my ($workbook,$filename,$format) =
+ &Apache::loncommon::create_workbook($r);
+ return if (! defined($workbook));
+
+ # Worksheet name
+ # The length of a worksheet name in Excel 95 is limited to 31 characters.
+ # Let's make sure to not exceed the maximum length in the translation files.
+ # Otherwise, no Excel file would be created.
+ my $worksheetname = &mt('Student Submission Data');
+ if (length($worksheetname) > 31) {
+ # nice way to cut off too long name
+ $worksheetname = substr($worksheetname,0,28,).'...';
+ }
+ my $worksheet = $workbook->addworksheet($worksheetname);
+ #
+ # Add headers to the worksheet
+ my $rows_output = 0;
+ $worksheet->write($rows_output++,0,
+ $env{'course.'.$env{'request.course.id'}.'.description'},
+ $format->{'h1'});
$rows_output++;
- $worksheet->write_row($rows_output++,0,\@Columns,$format->{'bold'});
+ my $cols_output = 0;
+ my $title_row = $rows_output++;
+ my $partid_row = $rows_output++;
+ my $respid_row = $rows_output++;
+ my $header_row = $rows_output++;
+ $worksheet->write($title_row ,0,&mt('Problem Title'),$format->{'bold'});
+ $worksheet->write($partid_row,0,&mt('Part ID'),$format->{'bold'});
+ $worksheet->write($respid_row,0,&mt('Response ID'),$format->{'bold'});
+ # Student headers
+ my @StudentColumns = &get_student_columns($show_named);
+ foreach my $field (@StudentColumns) {
+ $worksheet->write($header_row,$cols_output++,&mt(ucfirst($field)),
+ $format->{'bold'});
+ }
+ # Problem headers
+ my %start_col;
+ foreach my $prob (@$Problems) {
+ my $title = $prob->compTitle;
+ my $symb = $prob->symb();
+ $worksheet->write($title_row,$cols_output,
+ $title,$format->{'h3'});
+ foreach my $partid (@{$prob->parts}) {
+ if (($prob->is_anonsurvey($partid)) || ($anoncounter->{$symb."\0".$partid})) {
+ next if ($show_named);
+ } else {
+ next unless ($show_named);
+ }
+ $worksheet->write($partid_row,$cols_output,
+ $prob->part_display($partid));
+ my $responses = [$prob->responseIds($partid)];
+ my $resptypes = [$prob->responseType($partid)];
+ for (my $i=0;$isymb}->{$partid}->{$responses->[$i]}=
+ $cols_output;
+ $worksheet->write($respid_row,$cols_output,
+ $resptypes->[$i].', '.$responses->[$i]);
+ my @headers = &get_headers($prob,$partid,$responses->[$i],
+ $resptypes->[$i],
+ $problem_analysis{$prob->src},
+ 'excel','display',
+ @extra_resp_headers);
+ foreach my $text (@headers) {
+ if ($text eq 'Time') {
+ $worksheet->set_column($cols_output,$cols_output,undef,
+ $format->{'date'});
+ }
+ $worksheet->write($header_row,$cols_output++,$text);
+ }
+ }
+ }
+ }
#
# Populate the worksheet with the student data
+ my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,scalar(@$Students));
+ my $max_row = $rows_output;
foreach my $student (@$Students) {
last if ($c->aborted());
- my $results = &Apache::loncoursedata::get_response_data_by_student
- ($student,$resource->{'symb'},$respid);
- my %row;
- $row{'username'} = $student->{'username'};
- $row{'domain'} = $student->{'domain'};
- $row{'correct'} = $student->{'answer'};
- $row{'weight'} = &Apache::lonnet::EXT
- ('resource.'.$partid.'.weight',$resource->{'symb'},
- undef,undef,undef);
- if (! defined($results) || ref($results) ne 'ARRAY') {
- $row{'score'} = '='.
- &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
- ($rows_output,$awarded_col)
- .'*'.
- &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
- ($rows_output,$weight_col);
- my $cols_output = 0;
- foreach my $col (@Columns) {
- if (! exists($row{$col})) {
- $cols_output++;
- next;
+ $cols_output = 0;
+ my $student_row = $max_row;
+ foreach my $prob (@$Problems) {
+ my $symb = $prob->symb();
+ foreach my $partid (@{$prob->parts}) {
+ if (($prob->is_anonsurvey($partid)) || ($anoncounter->{$symb."\0".$partid})) {
+ next if ($show_named);
+ } else {
+ next unless ($show_named);
+ }
+ my @Response = $prob->responseIds($partid);
+ my @ResponseType = $prob->responseType($partid);
+ for (my $i=0;$i<=$#Response;$i++) {
+ my $respid = $Response[$i];
+ my $resptype = $ResponseType[$i];
+ my $results =
+ &Apache::loncoursedata::get_response_data_by_student
+ ($student,$prob->symb(),$respid);
+ my @headers = &get_headers($prob,$partid,$respid,
+ $resptype,
+ $problem_analysis{$prob->src},
+ 'excel','normal',
+ @extra_resp_headers);
+
+ if (! defined($results)) {
+ $results = [];
+ }
+ #
+ $rows_output = $student_row;
+ #
+ my $response_start_col = $start_col{$prob->symb}->{$partid}->{$respid};
+ for (my $j=scalar(@$results)-1;$j>=0;$j--) {
+ $cols_output = $response_start_col;
+ if ($env{'form.all_sub'} ne 'true') {
+ next if ($j ne scalar(@$results)-1);
+ }
+ my $response = &hashify_response($results->[$j],
+ $prob,
+ $student,
+ $partid,
+ $respid);
+ my @response_data =
+ &compile_response_data(\@headers,$response,
+ $prob,$partid,$respid,
+ $resptype,
+ \&excel_format_item);
+ $worksheet->write_row($rows_output++,$cols_output,
+ \@response_data);
+ $cols_output+=scalar(@response_data);
+ if ($rows_output > $max_row) {
+ $max_row = $rows_output;
+ }
+ }
}
- $worksheet->write($rows_output,$cols_output++,$row{$col});
}
- $rows_output++;
- } else {
- foreach my $response (@$results) {
- delete($row{'time'});
- delete($row{'attempt'});
- delete($row{'submission'});
- delete($row{'awarded'});
- delete($row{'grading'});
- delete($row{'score'});
- my %row_format;
- #
- # Time is handled differently
- $row{'time'} = &Apache::lonstathelpers::calc_serial
- ($response->[&Apache::loncoursedata::RDs_timestamp()]);
- $row_format{'time'}=$format->{'date'};
- #
- $row{'attempt'} = $response->[
- &Apache::loncoursedata::RDs_tries()];
- $row{'submission'} = $response->[
- &Apache::loncoursedata::RDs_submission()];
- if ($row{'submission'} =~ m/^=/) {
- # This will be interpreted as a formula. That is bad!
- $row{'submission'} = " ".$row{'submission'};
- }
- $row{'grading'} = $response->[
- &Apache::loncoursedata::RDs_awarddetail()];
- $row{'awarded'} = $response->[
- &Apache::loncoursedata::RDs_awarded()];
- $row{'score'} = '='.
- &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
- ($rows_output,$awarded_col)
- .'*'.
- &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
- ($rows_output,$weight_col);
- my $cols_output = 0;
- foreach my $col (@Columns) {
- $worksheet->write($rows_output,$cols_output++,$row{$col},
- $row_format{$col});
+ }
+ # Prepend current student's user information to all rows
+ for (my $row = $student_row;$row<$max_row;$row++) {
+ my $cols = 0;
+ foreach my $field (@StudentColumns) {
+ if ($show_named) {
+ my $value = $student->{$field};
+ if ($field eq 'comments') {
+ $value = &Apache::lonmsgdisplay::retrieve_instructor_comments
+ ($student->{'username'},$student->{'domain'});
+ }
+ $worksheet->write($row,$cols++,$value);
+ } else {
+ $worksheet->write($row,$cols++,
+ &mt('Anonymized'));
}
- $rows_output++;
}
- } # End of else clause on if (! defined($results) ....
+ }
+ &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student');
}
+ &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
#
# Close the excel file
$workbook->close();
@@ -288,7 +981,274 @@ sub prepare_excel_output {
'Done compiling spreadsheet. See link below to download.'.
'";');
$r->rflush();
+ return;
+}
+
+sub compile_response_data {
+ my ($headers,$response,$prob,$partid,$respid,$resptype,$format) = @_;
+ if (! ref($headers) || ref($headers) ne 'ARRAY' || ! scalar(@$headers)) {
+ return ();
+ }
+ if (ref($format) ne 'CODE') {
+ $format = sub { return $_[0]; };
+ }
+ #
+ my $submission =
+ &HTML::Entities::decode
+ (&unescape($response->{'Submission'}));
+ if (!$prob->is_task()) {
+ return () if (! defined($submission) || $submission eq '');
+ }
+ $submission =~ s/\\\"/\"/g;
+ $submission =~ s/\\\'/\'/g;
+ if ($resptype eq 'radiobutton') {
+ $submission =~ s/=([^=])$//;
+ }
+ $response->{'Submission'} = $submission;
+ #
+ my @values;
+ if ($resptype =~ /^(option|match|rank)$/) {
+ my %submission =
+ map {
+ my ($foil,$value) = split('=',&unescape($_));
+ ($foil,$value);
+ } split('&',$response->{'Submission'});
+ my %correct;
+ if (exists($response->{'Correct'})) {
+ %correct =
+ map {
+ my ($foil,$value)=split('=',&unescape($_));
+ ($foil,$value);
+ } split('&',$response->{'Correct'});
+ }
+ #
+ foreach my $original_header (@$headers) {
+ if ($original_header =~ /^_/) {
+ # '_' denotes a foil column
+ my ($header) = ($original_header =~ m/^_(.*)$/);
+ my $option = '';
+ if ( my ($foil) = ($header =~ /(.*) Correct$/)) {
+ if (exists($correct{$foil})) {
+ $option = $correct{$foil};
+ }
+ } elsif (exists($submission{$header})) {
+ $option = $submission{$header};
+ }
+ push(@values,&{$format}($option,$header));
+ } else {
+ # A normal column
+ push(@values,&{$format}($response->{$original_header},
+ $original_header));
+ }
+ }
+ } else {
+ @values = map { &{$format}($response->{$_},$_); } @$headers;
+ }
+ return @values;
+}
+
+sub excel_format_item {
+ my ($item,$type) = @_;
+ if ($type eq 'Time') {
+ $item = &Apache::lonstathelpers::calc_serial($item);
+ } else {
+ if ($item =~ m/^=/) {
+ $item = ' '.$item;
+ }
+ $item =~ s/\\r//g;
+ $item =~ s/\\n/\n/g;
+ $item =~ s/(\s*$|^\s*)//g;
+ $item =~ s/\\\'/\'/g;
+ }
+ return $item;
+}
+
+#########################################################
+#########################################################
+##
+## CSV output of student answers
+##
+#########################################################
+#########################################################
+sub prepare_csv_output {
+ my ($r,$problems,$students,$anoncounter,$show_named) = @_;
+ my $c = $r->connection();
+ #
+ $r->print('
'.
+ &mt('Generating CSV report of student responses').'
');
+ #
+ # Progress window
+ my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,scalar(@$students));
+
+ $r->rflush();
+ #
+ # Open a file
+ my $outputfile;
+ my $filename = '/prtspool/'.
+ $env{'user.name'}.'_'.$env{'user.domain'}.'_'.
+ time.'_'.rand(1000000000).'.csv';
+ unless ($outputfile = Apache::File->new('>/home/httpd'.$filename)) {
+ $r->log_error("Couldn't open $filename for output $!");
+ $r->print(
+ '
'
+ .&mt('Problems occurred in writing the CSV file.')
+ .' '.&mt('This error has been logged.')
+ .' '.&mt('Please alert your LON-CAPA administrator.')
+ .'
'
+ );
+ $outputfile = undef;
+ }
+ #
+ # Compute the number of columns per response
+ my @extra_resp_headers = &get_extra_response_headers($show_named);
+ #
+ # Create the table header
+ my @student_columns = &get_student_columns($show_named);
+ my $student_column_count = scalar(@student_columns);
+ #
+ my %headers;
+ push(@{$headers{'student'}},@student_columns);
+ # Pad for the student data
+ foreach my $row ('problem','part','response') {
+ $headers{$row}=[map {''} @student_columns];
+ }
+ #
+ # we put the headers into the %headers hash
+ my %problem_analysis;
+ my %start_col;
+ my $max_column = $student_column_count;
+ foreach my $prob (@$problems) {
+ my $symb = $prob->symb();
+ my %analysis = &Apache::lonstathelpers::get_problem_data($prob->src);
+ $problem_analysis{$prob->src}=\%analysis;
+ $headers{'problem'}->[$max_column] = $prob->compTitle;
+ foreach my $partid (@{$prob->parts}) {
+ if (($prob->is_anonsurvey($partid)) || ($anoncounter->{$symb."\0".$partid})) {
+ next if ($show_named);
+ } else {
+ next unless ($show_named);
+ }
+ $headers{'part'}->[$max_column] = $prob->part_display($partid);
+ my $responses = [$prob->responseIds($partid)];
+ my $resptypes = [$prob->responseType($partid)];
+ for (my $i=0;$i[$i],
+ $resptypes->[$i],
+ $problem_analysis{$prob->src},
+ 'csv','display',
+ @extra_resp_headers);
+ $start_col{$prob->symb}->{$partid}->{$responses->[$i]}=
+ $max_column;
+ $headers{'response'}->[$max_column]=
+ &mt('Response [_1]',$responses->[$i]);
+ for (my $j=0;$j<=$#headers;$j++) {
+ $headers{'student'}->[$max_column+$j]=$headers[$j];
+ }
+ $max_column += scalar(@headers);
+ }
+ }
+ }
+ foreach my $row ('problem','part','response','student') {
+ print $outputfile '"'.
+ join('","',
+ map {
+ &Apache::loncommon::csv_translate($_);
+ } @{$headers{$row}}).'"'.$/;
+ }
+ #
+ # Main loop
+ foreach my $student (@$students) {
+ last if ($c->aborted());
+ my @rows;
+ foreach my $prob (@$problems) {
+ my $symb = $prob->symb;
+ foreach my $partid (@{$prob->parts}) {
+ if (($prob->is_anonsurvey($partid)) || ($anoncounter->{$symb."\0".$partid})) {
+ next if ($show_named);
+ } else {
+ next unless ($show_named);
+ }
+ my @responses = $prob->responseIds($partid);
+ my @response_type = $prob->responseType($partid);
+ for (my $i=0;$i<=$#responses;$i++) {
+ my $respid = $responses[$i];
+ my $resptype = $response_type[$i];
+ my @headers = &get_headers($prob,$partid,$respid,$resptype,
+ $problem_analysis{$prob->src},
+ 'csv','normal',
+ @extra_resp_headers);
+ my $results =
+ &Apache::loncoursedata::get_response_data_by_student
+ ($student,$prob->symb(),$respid);
+ if (! defined($results)) {
+ $results = [];
+ }
+ for (my $j=0; $j[$idx],
+ $prob,$student,
+ $partid,$respid);
+ my @data = &compile_response_data(\@headers,$response,
+ $prob,$partid,
+ $respid,$resptype,
+ \&csv_format_item);
+ my $resp_start_idx =
+ $start_col{$prob->symb}->{$partid}->{$respid};
+ for (my $k=0;$k<=$#data;$k++) {
+ $rows[$j]->[$resp_start_idx + $k] = $data[$k];
+ }
+ }
+ }
+ }
+ }
+ foreach my $row (@rows) {
+ my $student_row_data = '';
+ if ($show_named) {
+ foreach my $field (@student_columns) {
+ my $value = $student->{$field};
+ if ($field eq 'comments') {
+ $value = &Apache::lonmsgdisplay::retrieve_instructor_comments
+ ($student->{'username'},$student->{'domain'});
+ }
+ $student_row_data .= '"'.&Apache::loncommon::csv_translate($value).'",';
+ }
+ $student_row_data =~ s/,$//;
+ } else {
+ $student_row_data = '"'.&mt('Anonymized').'"';
+ }
+ print $outputfile $student_row_data;
+ for (my $i=$student_column_count;$i<$max_column;$i++) {
+ my $value = &Apache::loncommon::csv_translate($row->[$i]);
+ $value ||='';
+ print $outputfile ',"'.$value.'"';
+ }
+ print $outputfile $/;
+ }
+ undef(@rows);
+ &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student');
+ }
+ close($outputfile);
+ #
+ # Close the progress window
+ &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
+ #
+ # Tell the user where to get their CSV file
+ $r->print(' '.
+ ''.&mt('Your CSV file.').''."\n");
+ $r->rflush();
+ return;
+}
+sub csv_format_item {
+ my ($item,$type) = @_;
+ if ($type eq 'Time') {
+ $item = &Apache::lonlocal::locallocaltime($item);
+ }
+ $item =&Apache::loncommon::csv_translate($item);
+ return $item;
}
#########################################################
@@ -300,37 +1260,114 @@ sub prepare_excel_output {
#########################################################
sub CreateInterface {
##
+ ## Output Selection
+ my $output_selector = $/.''.$/;
+ ##
## Environment variable initialization
my $Str = '';
- $Str .= &Apache::lonhtmlcommon::breadcrumbs
- (undef,'Student Submission Reports');
- $Str .= '