Annotation of loncom/interface/statistics/lonstudentassessment.pm, revision 1.87
1.1 stredwic 1: # The LearningOnline Network with CAPA
2: #
1.87 ! matthew 3: # $Id: lonstudentassessment.pm,v 1.86 2004/02/11 17:42:34 matthew Exp $
1.1 stredwic 4: #
5: # Copyright Michigan State University Board of Trustees
6: #
7: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
8: # LON-CAPA is free software; you can redistribute it and/or modify
9: # it under the terms of the GNU General Public License as published by
10: # the Free Software Foundation; either version 2 of the License, or
11: # (at your option) any later version.
12: #
13: # LON-CAPA is distributed in the hope that it will be useful,
14: # but WITHOUT ANY WARRANTY; without even the implied warranty of
15: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16: # GNU General Public License for more details.
17: #
18: # You should have received a copy of the GNU General Public License
19: # along with LON-CAPA; if not, write to the Free Software
20: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21: #
22: # /home/httpd/html/adm/gpl.txt
23: #
24: # http://www.lon-capa.org/
25: #
26: # (Navigate problems for statistical reports
1.28 matthew 27: #
28: #######################################################
29: #######################################################
30:
31: =pod
32:
33: =head1 NAME
34:
35: lonstudentassessment
36:
37: =head1 SYNOPSIS
38:
39: Presents assessment data about a student or a group of students.
40:
41: =head1 Subroutines
42:
43: =over 4
44:
45: =cut
46:
47: #######################################################
48: #######################################################
1.1 stredwic 49:
1.21 minaeibi 50: package Apache::lonstudentassessment;
1.1 stredwic 51:
52: use strict;
1.28 matthew 53: use Apache::lonstatistics;
1.1 stredwic 54: use Apache::lonhtmlcommon;
1.77 matthew 55: use Apache::loncommon();
1.1 stredwic 56: use Apache::loncoursedata;
1.28 matthew 57: use Apache::lonnet; # for logging porpoises
1.75 matthew 58: use Apache::lonlocal;
1.31 matthew 59: use Spreadsheet::WriteExcel;
1.76 matthew 60: use Spreadsheet::WriteExcel::Utility();
1.31 matthew 61:
62: #######################################################
63: #######################################################
64: =pod
65:
66: =item Package Variables
67:
68: =over 4
69:
70: =item $Statistics Hash ref to store student data. Indexed by symb,
71: contains hashes with keys 'score' and 'max'.
72:
73: =cut
74:
75: #######################################################
76: #######################################################
1.1 stredwic 77:
1.30 matthew 78: my $Statistics;
79:
1.28 matthew 80: #######################################################
81: #######################################################
82:
83: =pod
84:
1.31 matthew 85: =item $show_links 'yes' or 'no' for linking to student performance data
86:
87: =item $output_mode 'html', 'excel', or 'csv' for output mode
88:
1.32 matthew 89: =item $show 'all', 'totals', or 'scores' determines how much data is output
1.31 matthew 90:
1.49 matthew 91: =item $single_student_mode evaluates to true if we are showing only one
92: student.
93:
1.31 matthew 94: =cut
95:
96: #######################################################
97: #######################################################
98: my $show_links;
99: my $output_mode;
1.87 ! matthew 100: my $chosen_output;
1.49 matthew 101: my $single_student_mode;
1.28 matthew 102:
1.31 matthew 103: #######################################################
104: #######################################################
105: # End of package variable declarations
1.28 matthew 106:
1.31 matthew 107: =pod
1.28 matthew 108:
1.31 matthew 109: =back
1.28 matthew 110:
1.31 matthew 111: =cut
1.28 matthew 112:
1.31 matthew 113: #######################################################
114: #######################################################
1.28 matthew 115:
1.31 matthew 116: =pod
1.28 matthew 117:
1.31 matthew 118: =item &BuildStudentAssessmentPage()
1.28 matthew 119:
1.31 matthew 120: Inputs:
1.4 stredwic 121:
1.31 matthew 122: =over 4
1.28 matthew 123:
124: =item $r Apache Request
125:
126: =item $c Apache Connection
127:
128: =back
129:
130: =cut
131:
132: #######################################################
133: #######################################################
1.1 stredwic 134: sub BuildStudentAssessmentPage {
1.30 matthew 135: my ($r,$c)=@_;
1.74 matthew 136: #
1.30 matthew 137: undef($Statistics);
1.66 matthew 138: undef($show_links);
139: undef($output_mode);
1.87 ! matthew 140: undef($chosen_output);
1.66 matthew 141: undef($single_student_mode);
1.74 matthew 142: #
143: my %Saveable_Parameters = ('Status' => 'scalar',
144: 'chartoutputmode' => 'scalar',
145: 'chartoutputdata' => 'scalar',
146: 'Section' => 'array',
147: 'StudentData' => 'array',
148: 'Maps' => 'array');
149: &Apache::loncommon::store_course_settings('chart',\%Saveable_Parameters);
150: &Apache::loncommon::restore_course_settings('chart',\%Saveable_Parameters);
151: #
152: &Apache::lonstatistics::PrepareClasslist();
153: #
1.65 matthew 154: $single_student_mode = 0;
1.49 matthew 155: $single_student_mode = 1 if ($ENV{'form.SelectedStudent'});
1.59 matthew 156: if ($ENV{'form.selectstudent'}) {
157: &Apache::lonstatistics::DisplayClasslist($r);
158: return;
159: }
1.30 matthew 160: #
1.31 matthew 161: # Print out the HTML headers for the interface
162: # This also parses the output mode selector
1.54 matthew 163: # This step must *always* be done.
1.30 matthew 164: $r->print(&CreateInterface());
1.31 matthew 165: $r->print('<input type="hidden" name="notfirstrun" value="true" />');
1.49 matthew 166: $r->print('<input type="hidden" name="sort" value="'.
167: $ENV{'form.sort'}.'" />');
1.7 stredwic 168: $r->rflush();
1.58 matthew 169: #
1.49 matthew 170: if (! exists($ENV{'form.notfirstrun'}) && ! $single_student_mode) {
1.31 matthew 171: return;
172: }
173: #
174: my $initialize = \&html_initialize;
175: my $output_student = \&html_outputstudent;
176: my $finish = \&html_finish;
177: #
178: if ($output_mode eq 'excel') {
179: $initialize = \&excel_initialize;
180: $output_student = \&excel_outputstudent;
181: $finish = \&excel_finish;
182: } elsif ($output_mode eq 'csv') {
183: $initialize = \&csv_initialize;
184: $output_student = \&csv_outputstudent;
185: $finish = \&csv_finish;
186: }
1.30 matthew 187: #
188: if($c->aborted()) { return ; }
1.31 matthew 189: #
1.49 matthew 190: # Determine which students we want to look at
191: my @Students;
192: if ($single_student_mode) {
193: @Students = (&Apache::lonstatistics::current_student());
194: $r->print(&next_and_previous_buttons());
195: $r->rflush();
196: } else {
197: @Students = @Apache::lonstatistics::Students;
198: }
1.56 matthew 199: #
200: # Perform generic initialization tasks
201: # Since we use lonnet::EXT to retrieve problem weights,
202: # to ensure current data we must clear the caches out.
203: # This makes sure that parameter changes at the student level
204: # are immediately reflected in the chart.
205: &Apache::lonnet::clear_EXT_cache_status();
1.69 matthew 206: #
207: # Clean out loncoursedata's package data, just to be safe.
208: &Apache::loncoursedata::clear_internal_caches();
1.49 matthew 209: #
1.31 matthew 210: # Call the initialize routine selected above
211: $initialize->($r);
1.49 matthew 212: foreach my $student (@Students) {
1.31 matthew 213: if($c->aborted()) {
214: $finish->($r);
215: return ;
1.1 stredwic 216: }
1.31 matthew 217: # Call the output_student routine selected above
218: $output_student->($r,$student);
219: }
220: # Call the "finish" routine selected above
221: $finish->($r);
222: #
223: return;
224: }
225:
226: #######################################################
227: #######################################################
1.49 matthew 228: sub next_and_previous_buttons {
229: my $Str = '';
230: $Str .= '<input type="hidden" name="SelectedStudent" value="'.
231: $ENV{'form.SelectedStudent'}.'" />';
232: #
233: # Build the previous student link
234: my $previous = &Apache::lonstatistics::previous_student();
235: my $previousbutton = '';
236: if (defined($previous)) {
237: my $sname = $previous->{'username'}.':'.$previous->{'domain'};
238: $previousbutton .= '<input type="button" value="'.
239: 'Previous Student ('.
240: $previous->{'username'}.'@'.$previous->{'domain'}.')'.
241: '" onclick="document.Statistics.SelectedStudent.value='.
242: "'".$sname."'".';'.
243: 'document.Statistics.submit();" />';
244: } else {
245: $previousbutton .= '<input type="button" value="'.
246: 'Previous student (none)'.'" />';
247: }
248: #
249: # Build the next student link
250: my $next = &Apache::lonstatistics::next_student();
251: my $nextbutton = '';
252: if (defined($next)) {
253: my $sname = $next->{'username'}.':'.$next->{'domain'};
254: $nextbutton .= '<input type="button" value="'.
255: 'Next Student ('.
256: $next->{'username'}.'@'.$next->{'domain'}.')'.
257: '" onclick="document.Statistics.SelectedStudent.value='.
258: "'".$sname."'".';'.
259: 'document.Statistics.submit();" />';
260: } else {
261: $nextbutton .= '<input type="button" value="'.
262: 'Next student (none)'.'" />';
263: }
264: #
265: # Build the 'all students' button
266: my $all = '';
267: $all .= '<input type="button" value="All Students" '.
268: '" onclick="document.Statistics.SelectedStudent.value='.
269: "''".';'.'document.Statistics.submit();" />';
270: $Str .= $previousbutton.(' 'x5).$all.(' 'x5).$nextbutton;
271: return $Str;
272: }
273:
274: #######################################################
275: #######################################################
1.30 matthew 276:
1.31 matthew 277: sub get_student_fields_to_show {
278: my @to_show = @Apache::lonstatistics::SelectedStudentData;
279: foreach (@to_show) {
280: if ($_ eq 'all') {
281: @to_show = @Apache::lonstatistics::StudentDataOrder;
282: last;
283: }
284: }
285: return @to_show;
286: }
287:
1.28 matthew 288: #######################################################
289: #######################################################
290:
291: =pod
1.2 stredwic 292:
1.28 matthew 293: =item &CreateInterface()
1.21 minaeibi 294:
1.28 matthew 295: Called by &BuildStudentAssessmentPage to create the top part of the
296: page which displays the chart.
297:
1.30 matthew 298: Inputs: None
1.28 matthew 299:
300: Returns: A string containing the HTML for the headers and top table for
301: the chart page.
302:
303: =cut
304:
305: #######################################################
306: #######################################################
1.2 stredwic 307: sub CreateInterface {
1.4 stredwic 308: my $Str = '';
1.30 matthew 309: # $Str .= &CreateLegend();
310: $Str .= '<table cellspacing="5">'."\n";
311: $Str .= '<tr>';
1.75 matthew 312: $Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>';
313: $Str .= '<td align="center"><b>'.&mt('Student Data</b>').'</td>';
314: $Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>';
315: $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b></td>';
316: $Str .= '<td align="center"><b>'.&mt('Output Format').'</b>'.
1.58 matthew 317: &Apache::loncommon::help_open_topic("Chart_Output_Formats").
318: '</td>';
1.75 matthew 319: $Str .= '<td align="center"><b>'.&mt('Output Data').'</b>'.
1.58 matthew 320: &Apache::loncommon::help_open_topic("Chart_Output_Data").
321: '</td>';
1.30 matthew 322: $Str .= '</tr>'."\n";
323: #
1.4 stredwic 324: $Str .= '<tr><td align="center">'."\n";
1.29 matthew 325: $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
1.4 stredwic 326: $Str .= '</td><td align="center">';
1.30 matthew 327: my $only_seq_with_assessments = sub {
328: my $s=shift;
329: if ($s->{'num_assess'} < 1) {
330: return 0;
331: } else {
332: return 1;
333: }
334: };
335: $Str .= &Apache::lonstatistics::StudentDataSelect('StudentData','multiple',
336: 5,undef);
1.46 matthew 337: $Str .= '</td><td>'."\n";
338: $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
1.4 stredwic 339: $Str .= '</td><td>'."\n";
1.30 matthew 340: $Str .= &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
341: $only_seq_with_assessments);
1.31 matthew 342: $Str .= '</td><td>'."\n";
343: $Str .= &CreateAndParseOutputSelector();
1.54 matthew 344: $Str .= '</td><td>'."\n";
345: $Str .= &CreateAndParseOutputDataSelector();
1.30 matthew 346: $Str .= '</td></tr>'."\n";
347: $Str .= '</table>'."\n";
1.75 matthew 348: $Str .= '<input type="submit" name="Generate Chart" value="'.
349: &mt('Generate Chart').'" />';
1.59 matthew 350: $Str .= ' 'x5;
1.75 matthew 351: $Str .= '<input type="submit" name="selectstudent" value="'.
352: &mt('Select One Student').'" />';
1.59 matthew 353: $Str .= ' 'x5;
1.75 matthew 354: $Str .= '<input type="submit" name="ClearCache" value="'.
355: &mt('Clear Caches').'" />';
1.61 www 356: $Str .= ' 'x5;
357: $Str .= '<br />';
1.4 stredwic 358: return $Str;
1.1 stredwic 359: }
1.30 matthew 360:
361: #######################################################
362: #######################################################
363:
364: =pod
365:
1.31 matthew 366: =item &CreateAndParseOutputSelector()
1.30 matthew 367:
368: =cut
369:
370: #######################################################
371: #######################################################
1.32 matthew 372: my @OutputOptions =
373: ({ name => 'HTML, with links',
374: value => 'html, with links',
1.33 matthew 375: description => 'Output HTML with each symbol linked to the problem '.
1.35 matthew 376: 'which generated it.',
377: mode => 'html',
378: show_links => 'yes',
379: },
1.47 matthew 380: { name => 'HTML, with all links',
381: value => 'html, with all links',
382: description => 'Output HTML with each symbol linked to the problem '.
383: 'which generated it. '.
384: 'This includes links for unattempted problems.',
385: mode => 'html',
386: show_links => 'all',
387: },
1.32 matthew 388: { name => 'HTML, without links',
389: value => 'html, without links',
1.33 matthew 390: description => 'Output HTML. By not including links, the size of the'.
391: ' web page is greatly reduced. If your browser crashes on the '.
1.35 matthew 392: 'full display, try this.',
393: mode => 'html',
394: show_links => 'no',
395: },
1.54 matthew 396: { name => 'Excel',
397: value => 'excel',
398: description => 'Output an Excel file (compatable with Excel 95).',
1.35 matthew 399: mode => 'excel',
400: show_links => 'no',
1.54 matthew 401: },
402: { name => 'CSV',
403: value => 'csv',
404: description => 'Output a comma seperated values file suitable for '.
1.57 matthew 405: 'import into a spreadsheet program. Using this method as opposed '.
406: 'to Excel output allows you to organize your data before importing'.
407: ' it into a spreadsheet program.',
1.35 matthew 408: mode => 'csv',
409: show_links => 'no',
410: },
1.32 matthew 411: );
412:
1.33 matthew 413: sub OutputDescriptions {
414: my $Str = '';
1.58 matthew 415: $Str .= "<h2>Output Formats</h2>\n";
1.33 matthew 416: $Str .= "<dl>\n";
417: foreach my $outputmode (@OutputOptions) {
418: $Str .=" <dt>".$outputmode->{'name'}."</dt>\n";
419: $Str .=" <dd>".$outputmode->{'description'}."</dd>\n";
420: }
421: $Str .= "</dl>\n";
422: return $Str;
423: }
424:
1.31 matthew 425: sub CreateAndParseOutputSelector {
426: my $Str = '';
1.44 matthew 427: my $elementname = 'chartoutputmode';
1.73 matthew 428: &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},
429: [$elementname]);
1.31 matthew 430: #
431: # Format for output options is 'mode, restrictions';
1.50 matthew 432: my $selected = 'html, without links';
1.31 matthew 433: if (exists($ENV{'form.'.$elementname})) {
434: if (ref($ENV{'form.'.$elementname} eq 'ARRAY')) {
435: $selected = $ENV{'form.'.$elementname}->[0];
436: } else {
437: $selected = $ENV{'form.'.$elementname};
438: }
439: }
440: #
441: # Set package variables describing output mode
442: $show_links = 'no';
443: $output_mode = 'html';
1.35 matthew 444: foreach my $option (@OutputOptions) {
445: next if ($option->{'value'} ne $selected);
446: $output_mode = $option->{'mode'};
447: $show_links = $option->{'show_links'};
1.31 matthew 448: }
1.35 matthew 449:
1.31 matthew 450: #
451: # Build the form element
452: $Str = qq/<select size="5" name="$elementname">/;
1.32 matthew 453: foreach my $option (@OutputOptions) {
454: $Str .= "\n".' <option value="'.$option->{'value'}.'"';
455: $Str .= " selected " if ($option->{'value'} eq $selected);
1.75 matthew 456: $Str .= ">".&mt($option->{'name'})."<\/option>";
1.31 matthew 457: }
458: $Str .= "\n</select>";
459: return $Str;
460: }
1.30 matthew 461:
1.54 matthew 462: ##
463: ## Data selector stuff
464: ##
465: my @OutputDataOptions =
1.57 matthew 466: (
1.72 matthew 467: { name => 'Scores Summary',
1.57 matthew 468: base => 'scores',
469: value => 'sum and total',
1.87 ! matthew 470: scores => 1,
! 471: tries => 0,
! 472: every_problem => 0,
! 473: sequence_sum => 1,
! 474: sequence_max => 1,
! 475: grand_total => 1,
1.57 matthew 476: shortdesc => 'Total Score and Maximum Possible for each '.
477: 'Sequence or Folder',
478: longdesc => 'The score of each student as well as the '.
479: ' maximum possible on each Sequence or Folder.',
480: },
1.71 matthew 481: { name => 'Scores Per Problem',
1.57 matthew 482: base => 'scores',
1.71 matthew 483: value => 'scores',
1.87 ! matthew 484: scores => 1,
! 485: tries => 0,
! 486: correct => 0,
! 487: every_problem => 1,
! 488: sequence_sum => 1,
! 489: sequence_max => 1,
! 490: grand_total => 1,
1.71 matthew 491: shortdesc => 'Score on each Problem Part',
492: longdesc =>'The students score on each problem part, computed as'.
493: 'the part weight * part awarded',
1.57 matthew 494: },
495: { name =>'Tries',
496: base =>'tries',
497: value => 'tries',
1.87 ! matthew 498: scores => 0,
! 499: tries => 1,
! 500: correct => 0,
! 501: every_problem => 1,
! 502: sequence_sum => 0,
! 503: sequence_max => 0,
! 504: grand_total => 0,
1.57 matthew 505: shortdesc => 'Number of Tries before success on each Problem Part',
506: longdesc =>'The number of tries before success on each problem part.',
507: },
508: { name =>'Parts Correct',
509: base =>'tries',
510: value => 'parts correct total',
1.87 ! matthew 511: scores => 0,
! 512: tries => 0,
! 513: correct => 1,
! 514: every_problem => 1,
! 515: sequence_sum => 1,
! 516: sequence_max => 1,
! 517: grand_total => 1,
1.57 matthew 518: shortdesc => 'Number of Problem Parts completed successfully.',
519: longdesc => 'The Number of Problem Parts completed successfully and '.
520: 'the maximum possible for each student',
521: },
522: );
523:
524: sub HTMLifyOutputDataDescriptions {
525: my $Str = '';
1.58 matthew 526: $Str .= "<h2>Output Data</h2>\n";
1.57 matthew 527: $Str .= "<dl>\n";
528: foreach my $option (@OutputDataOptions) {
529: $Str .= ' <dt>'.$option->{'name'}.'</dt>';
530: $Str .= '<dd>'.$option->{'longdesc'}.'</dd>'."\n";
531: }
532: $Str .= "</dl>\n";
533: return $Str;
534: }
1.54 matthew 535:
536: sub CreateAndParseOutputDataSelector {
537: my $Str = '';
538: my $elementname = 'chartoutputdata';
539: #
540: my $selected = 'scores';
541: if (exists($ENV{'form.'.$elementname})) {
542: if (ref($ENV{'form.'.$elementname} eq 'ARRAY')) {
543: $selected = $ENV{'form.'.$elementname}->[0];
544: } else {
545: $selected = $ENV{'form.'.$elementname};
546: }
547: }
548: #
1.87 ! matthew 549: $chosen_output = $OutputDataOptions[0];
1.54 matthew 550: foreach my $option (@OutputDataOptions) {
551: if ($option->{'value'} eq $selected) {
1.87 ! matthew 552: $chosen_output = $option;
1.54 matthew 553: }
554: }
555: #
556: # Build the form element
557: $Str = qq/<select size="5" name="$elementname">/;
558: foreach my $option (@OutputDataOptions) {
559: $Str .= "\n".' <option value="'.$option->{'value'}.'"';
1.87 ! matthew 560: $Str .= " selected " if ($option->{'value'} eq $chosen_output->{'value'});
1.75 matthew 561: $Str .= ">".&mt($option->{'name'})."<\/option>";
1.54 matthew 562: }
563: $Str .= "\n</select>";
564: return $Str;
565:
566: }
567:
1.28 matthew 568: #######################################################
569: #######################################################
1.1 stredwic 570:
1.28 matthew 571: =pod
572:
1.31 matthew 573: =head2 HTML output routines
1.28 matthew 574:
1.31 matthew 575: =item &html_initialize($r)
1.28 matthew 576:
1.31 matthew 577: Create labels for the columns of student data to show.
1.28 matthew 578:
1.31 matthew 579: =item &html_outputstudent($r,$student)
1.28 matthew 580:
1.31 matthew 581: Return a line of the chart for a student.
1.28 matthew 582:
1.31 matthew 583: =item &html_finish($r)
1.28 matthew 584:
585: =cut
586:
587: #######################################################
588: #######################################################
1.31 matthew 589: {
590: my $padding;
591: my $count;
592:
1.39 matthew 593: my $nodata_count; # The number of students for which there is no data
594: my %prog_state; # progress state used by loncommon PrgWin routines
595:
1.31 matthew 596: sub html_initialize {
597: my ($r) = @_;
1.30 matthew 598: #
599: $padding = ' 'x3;
1.35 matthew 600: $count = 0;
1.39 matthew 601: $nodata_count = 0;
1.66 matthew 602: undef(%prog_state);
1.30 matthew 603: #
1.38 matthew 604: $r->print("<h3>".$ENV{'course.'.$ENV{'request.course.id'}.'.description'}.
605: " ".localtime(time)."</h3>");
1.39 matthew 606:
1.87 ! matthew 607: if ($chosen_output->{'base'} !~ /^final table/) {
! 608: $r->print("<h3>".$chosen_output->{'shortdesc'}."</h3>");
1.39 matthew 609: }
1.31 matthew 610: my $Str = "<pre>\n";
1.30 matthew 611: # First, the @StudentData fields need to be listed
1.31 matthew 612: my @to_show = &get_student_fields_to_show();
1.30 matthew 613: foreach my $field (@to_show) {
614: my $title=$Apache::lonstatistics::StudentData{$field}->{'title'};
615: my $base =$Apache::lonstatistics::StudentData{$field}->{'base_width'};
616: my $width=$Apache::lonstatistics::StudentData{$field}->{'width'};
617: $Str .= $title.' 'x($width-$base).$padding;
618: }
619: # Now the selected sequences need to be listed
1.40 matthew 620: foreach my $sequence (&Apache::lonstatistics::Sequences_with_Assess()){
1.31 matthew 621: my $title = $sequence->{'title'};
622: my $base = $sequence->{'base_width'};
623: my $width = $sequence->{'width'};
624: $Str .= $title.' 'x($width-$base).$padding;
1.30 matthew 625: }
1.54 matthew 626: $Str .= "total</pre>\n";
1.31 matthew 627: $Str .= "<pre>";
628: $r->print($Str);
629: $r->rflush();
630: return;
1.30 matthew 631: }
632:
1.31 matthew 633: sub html_outputstudent {
634: my ($r,$student) = @_;
1.2 stredwic 635: my $Str = '';
1.35 matthew 636: #
1.87 ! matthew 637: if($count++ % 5 == 0 && $count > 0) {
1.35 matthew 638: $r->print("</pre><pre>");
639: }
1.30 matthew 640: # First, the @StudentData fields need to be listed
1.31 matthew 641: my @to_show = &get_student_fields_to_show();
1.30 matthew 642: foreach my $field (@to_show) {
643: my $title=$student->{$field};
1.31 matthew 644: my $base = length($title);
1.30 matthew 645: my $width=$Apache::lonstatistics::StudentData{$field}->{'width'};
646: $Str .= $title.' 'x($width-$base).$padding;
647: }
648: # Get ALL the students data
649: my %StudentsData;
650: my @tmp = &Apache::loncoursedata::get_current_state
651: ($student->{'username'},$student->{'domain'},undef,
652: $ENV{'request.course.id'});
653: if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) {
654: %StudentsData = @tmp;
655: }
656: if (scalar(@tmp) < 1) {
1.39 matthew 657: $nodata_count++;
1.30 matthew 658: $Str .= '<font color="blue">No Course Data</font>'."\n";
1.31 matthew 659: $r->print($Str);
660: $r->rflush();
661: return;
1.30 matthew 662: }
663: #
664: # By sequence build up the data
665: my $studentstats;
1.31 matthew 666: my $PerformanceStr = '';
1.40 matthew 667: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
1.54 matthew 668: my ($performance,$performance_length,$score,$seq_max,$rawdata);
1.87 ! matthew 669: if ($chosen_output->{'tries'}) {
1.54 matthew 670: ($performance,$performance_length,$score,$seq_max,$rawdata) =
671: &StudentTriesOnSequence($student,\%StudentsData,
672: $seq,$show_links);
673: } else {
674: ($performance,$performance_length,$score,$seq_max,$rawdata) =
675: &StudentPerformanceOnSequence($student,\%StudentsData,
676: $seq,$show_links);
677: }
678: my $ratio = sprintf("%3d",$score).'/'.sprintf("%3d",$seq_max);
1.31 matthew 679: #
1.87 ! matthew 680: if ($chosen_output->{'sequence_sum'}) {
1.54 matthew 681: $performance = $ratio;
682: $performance .= ' 'x($seq->{'width'}-length($ratio));
1.31 matthew 683: } else {
684: # Pad with extra spaces
1.51 matthew 685: $performance .= ' 'x($seq->{'width'}-$performance_length-
1.31 matthew 686: length($ratio)
687: ).$ratio;
1.30 matthew 688: }
1.31 matthew 689: #
690: $Str .= $performance.$padding;
691: #
692: $studentstats->{$seq->{'symb'}}->{'score'}= $score;
693: $studentstats->{$seq->{'symb'}}->{'max'} = $seq_max;
1.30 matthew 694: }
695: #
696: # Total it up and store the statistics info.
697: my ($score,$max) = (0,0);
698: while (my ($symb,$seq_stats) = each (%{$studentstats})) {
699: $Statistics->{$symb}->{'score'} += $seq_stats->{'score'};
1.54 matthew 700: if ($Statistics->{$symb}->{'max'} < $seq_stats->{'max'}) {
701: $Statistics->{$symb}->{'max'} = $seq_stats->{'max'};
702: }
1.30 matthew 703: $score += $seq_stats->{'score'};
704: $max += $seq_stats->{'max'};
705: }
1.31 matthew 706: $Str .= ' '.' 'x(length($max)-length($score)).$score.'/'.$max;
1.30 matthew 707: $Str .= " \n";
1.39 matthew 708: #
1.31 matthew 709: $r->print($Str);
710: #
711: $r->rflush();
712: return;
1.30 matthew 713: }
1.2 stredwic 714:
1.31 matthew 715: sub html_finish {
716: my ($r) = @_;
1.39 matthew 717: #
718: # Check for suppressed output and close the progress window if so
1.87 ! matthew 719: $r->print("</pre>\n");
1.49 matthew 720: if ($single_student_mode) {
721: $r->print(&SingleStudentTotal());
722: } else {
723: $r->print(&StudentAverageTotal());
724: }
1.31 matthew 725: $r->rflush();
726: return;
727: }
728:
1.39 matthew 729: sub StudentAverageTotal {
730: my $Str = "<h3>Summary Tables</h3>\n";
731: my $num_students = scalar(@Apache::lonstatistics::Students);
732: my $total_ave = 0;
733: my $total_max = 0;
734: $Str .= '<table border=2 cellspacing="1">'."\n";
735: $Str .= "<tr><th>Title</th><th>Average</th><th>Maximum</th></tr>\n";
1.40 matthew 736: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
1.42 matthew 737: my $ave;
738: if ($num_students > $nodata_count) {
739: $ave = int(100*($Statistics->{$seq->{'symb'}}->{'score'}/
740: ($num_students-$nodata_count)))/100;
741: } else {
742: $ave = 0;
743: }
1.39 matthew 744: $total_ave += $ave;
1.54 matthew 745: my $max = $Statistics->{$seq->{'symb'}}->{'max'};
1.39 matthew 746: $total_max += $max;
1.82 matthew 747: $ave = sprintf("%.2f",$ave);
1.39 matthew 748: $Str .= '<tr><td>'.$seq->{'title'}.'</td>'.
1.82 matthew 749: '<td align="right">'.$ave.' </td>'.
750: '<td align="right">'.$max.' '.'</td></tr>'."\n";
1.39 matthew 751: }
1.82 matthew 752: $total_ave = sprintf('%.2f',$total_ave); # only two digit
1.39 matthew 753: $Str .= "</table>\n";
754: $Str .= '<table border=2 cellspacing="1">'."\n";
755: $Str .= '<tr><th>Number of Students</th><th>Average</th>'.
756: "<th>Maximum</th></tr>\n";
1.82 matthew 757: $Str .= '<tr>'.
758: '<td align="right">'.($num_students-$nodata_count).'</td>'.
759: '<td align="right">'.$total_ave.' '.'</td>'.
760: '<td align="right">'.$total_max.' '.'</td>';
1.39 matthew 761: $Str .= "</table>\n";
762: return $Str;
763: }
764:
1.49 matthew 765: sub SingleStudentTotal {
766: my $student = &Apache::lonstatistics::current_student();
1.52 matthew 767: my $Str = "<h3>Summary table for ".$student->{'fullname'}." ".
768: $student->{'username'}.'@'.$student->{'domain'}."</h3>\n";
1.49 matthew 769: $Str .= '<table border=2 cellspacing="1">'."\n";
770: $Str .=
771: "<tr><th>Sequence or Folder</th><th>Score</th><th>Maximum</th></tr>\n";
772: my $total = 0;
773: my $total_max = 0;
774: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
775: my $value = $Statistics->{$seq->{'symb'}}->{'score'};
776: my $max = $Statistics->{$seq->{'symb'}}->{'max'};
777: $Str .= '<tr><td>'.$seq->{'title'}.'</td>'.
778: '<td align="right">'.$value.'</td>'.
779: '<td align="right">'.$max.'</td></tr>'."\n";
780: $total += $value;
781: $total_max +=$max;
782: }
783: $Str .= '<tr><td><b>Total</b></td>'.
784: '<td align="right">'.$total.'</td>'.
785: '<td align="right">'.$total_max."</td></tr>\n";
786: $Str .= "</table>\n";
787: return $Str;
788: }
789:
1.31 matthew 790: }
791:
792: #######################################################
793: #######################################################
794:
795: =pod
796:
797: =head2 EXCEL subroutines
798:
799: =item &excel_initialize($r)
800:
801: =item &excel_outputstudent($r,$student)
802:
803: =item &excel_finish($r)
804:
805: =cut
806:
807: #######################################################
808: #######################################################
809: {
810:
811: my $excel_sheet;
1.32 matthew 812: my $excel_workbook;
813:
814: my $filename;
815: my $rows_output;
816: my $cols_output;
817:
1.36 matthew 818: my %prog_state; # progress window state
1.54 matthew 819: my $request_aborted;
1.31 matthew 820:
1.76 matthew 821: my $total_formula;
822:
1.31 matthew 823: sub excel_initialize {
824: my ($r) = @_;
825: #
1.66 matthew 826: undef ($excel_sheet);
827: undef ($excel_workbook);
828: undef ($filename);
829: undef ($rows_output);
830: undef ($cols_output);
831: undef (%prog_state);
832: undef ($request_aborted);
1.76 matthew 833: undef ($total_formula);
1.66 matthew 834: #
1.54 matthew 835: my $total_columns = scalar(&get_student_fields_to_show());
836: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
1.87 ! matthew 837: # Add 2 because we need a 'sequence_sum' and 'total' column for each
1.54 matthew 838: $total_columns += $seq->{'num_assess_parts'}+2;
839: }
1.87 ! matthew 840: if ($chosen_output->{'base'} eq 'tries' && $total_columns > 255) {
1.54 matthew 841: $r->print(<<END);
842: <h2>Unable to Complete Request</h2>
843: <p>
844: LON-CAPA is unable to produce your Excel spreadsheet because your selections
845: will result in more than 255 columns. Excel allows only 255 columns in a
846: spreadsheet.
847: </p><p>
848: You may consider reducing the number of <b>Sequences or Folders</b> you
849: have selected.
850: </p><p>
851: LON-CAPA can produce <b>CSV</b> files of this data or Excel files of the
852: summary data (<b>Parts Correct</b> or <b>Parts Correct & Totals</b>).
853: </p>
854: END
855: $request_aborted = 1;
856: }
1.87 ! matthew 857: if ($chosen_output->{'base'} eq 'scores' && $total_columns > 255) {
1.54 matthew 858: $r->print(<<END);
859: <h2>Unable to Complete Request</h2>
860: <p>
861: LON-CAPA is unable to produce your Excel spreadsheet because your selections
862: will result in more than 255 columns. Excel allows only 255 columns in a
863: spreadsheet.
864: </p><p>
865: You may consider reducing the number of <b>Sequences or Folders</b> you
866: have selected.
867: </p><p>
868: LON-CAPA can produce <b>CSV</b> files of this data or Excel files of the
1.80 matthew 869: <b>Scores Summary</b> data.
1.54 matthew 870: </p>
871: END
872: $request_aborted = 1;
873: }
874: return if ($request_aborted);
875: #
1.32 matthew 876: $filename = '/prtspool/'.
1.31 matthew 877: $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
878: time.'_'.rand(1000000000).'.xls';
1.32 matthew 879: #
880: $excel_workbook = undef;
881: $excel_sheet = undef;
882: #
883: $rows_output = 0;
884: $cols_output = 0;
885: #
886: # Create sheet
887: $excel_workbook = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);
888: #
889: # Check for errors
890: if (! defined($excel_workbook)) {
1.31 matthew 891: $r->log_error("Error creating excel spreadsheet $filename: $!");
892: $r->print("Problems creating new Excel file. ".
893: "This error has been logged. ".
894: "Please alert your LON-CAPA administrator");
1.32 matthew 895: return ;
1.31 matthew 896: }
897: #
898: # The excel spreadsheet stores temporary data in files, then put them
899: # together. If needed we should be able to disable this (memory only).
900: # The temporary directory must be specified before calling 'addworksheet'.
901: # File::Temp is used to determine the temporary directory.
1.32 matthew 902: $excel_workbook->set_tempdir($Apache::lonnet::tmpdir);
903: #
904: # Add a worksheet
1.33 matthew 905: my $sheetname = $ENV{'course.'.$ENV{'request.course.id'}.'.description'};
1.67 matthew 906: $sheetname = &Apache::loncommon::clean_excel_name($sheetname);
1.33 matthew 907: $excel_sheet = $excel_workbook->addworksheet($sheetname);
1.32 matthew 908: #
1.85 matthew 909: # Define some potentially useful formats
910: my $format;
911: $format->{'header'} = $excel_workbook->add_format(bold => 1,
912: bottom => 1,
913: align => 'center');
914: $format->{'bold'} = $excel_workbook->add_format(bold=>1);
915: $format->{'h1'} = $excel_workbook->add_format(bold=>1, size=>18);
916: $format->{'h2'} = $excel_workbook->add_format(bold=>1, size=>16);
917: $format->{'h3'} = $excel_workbook->add_format(bold=>1, size=>14);
918: $format->{'date'} = $excel_workbook->add_format(num_format=>
919: 'mmm d yyyy hh:mm AM/PM');
920: #
1.34 matthew 921: # Put the course description in the header
922: $excel_sheet->write($rows_output,$cols_output++,
1.85 matthew 923: $ENV{'course.'.$ENV{'request.course.id'}.'.description'},
924: $format->{'h1'});
1.34 matthew 925: $cols_output += 3;
926: #
927: # Put a description of the sections listed
928: my $sectionstring = '';
929: my @Sections = @Apache::lonstatistics::SelectedSections;
930: if (scalar(@Sections) > 1) {
931: if (scalar(@Sections) > 2) {
932: my $last = pop(@Sections);
933: $sectionstring = "Sections ".join(', ',@Sections).', and '.$last;
934: } else {
935: $sectionstring = "Sections ".join(' and ',@Sections);
936: }
937: } else {
938: if ($Sections[0] eq 'all') {
939: $sectionstring = "All sections";
940: } else {
941: $sectionstring = "Section ".$Sections[0];
942: }
943: }
1.85 matthew 944: $excel_sheet->write($rows_output,$cols_output++,$sectionstring,
945: $format->{'h3'});
1.34 matthew 946: $cols_output += scalar(@Sections);
947: #
948: # Put the date in there too
1.54 matthew 949: $excel_sheet->write($rows_output++,$cols_output++,
1.85 matthew 950: 'Compiled on '.localtime(time),$format->{'h3'});
1.34 matthew 951: #
1.54 matthew 952: $cols_output = 0;
1.87 ! matthew 953: $excel_sheet->write($rows_output++,$cols_output++,
! 954: $chosen_output->{'shortdesc'},
1.85 matthew 955: $format->{'h3'});
1.54 matthew 956: #
1.86 matthew 957: # Figure out the rows we need
958: my $sequence_name_row = $rows_output+1;
959: my $resource_name_row = $sequence_name_row+1;
960: my $maximum_data_row = $sequence_name_row+2;
961: my $first_data_row = $sequence_name_row+3;
962: #
1.32 matthew 963: # Add the student headers
1.34 matthew 964: $cols_output = 0;
1.32 matthew 965: foreach my $field (&get_student_fields_to_show()) {
1.86 matthew 966: $excel_sheet->write($resource_name_row,$cols_output++,$field,
1.85 matthew 967: $format->{'bold'});
1.32 matthew 968: }
969: #
1.54 matthew 970: # Add the remaining column headers
1.76 matthew 971: my $total_formula_string = '=0';
1.40 matthew 972: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
1.86 matthew 973: $excel_sheet->write($sequence_name_row,,
1.85 matthew 974: $cols_output,$seq->{'title'},$format->{'h3'});
1.86 matthew 975: # Determine starting cell
976: $seq->{'Excel:startcell'}=
977: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
978: ($maximum_data_row,$cols_output);
979: $seq->{'Excel:startcol'}=$cols_output;
1.87 ! matthew 980: my $count = 0;
! 981: if ($chosen_output->{'every_problem'}) {
1.76 matthew 982: # Put the names of the problems and parts into the sheet
1.54 matthew 983: foreach my $res (@{$seq->{'contents'}}) {
1.84 matthew 984: if ($res->{'type'} ne 'assessment' ||
985: ! exists($res->{'parts'}) ||
986: ref($res->{'parts'}) ne 'ARRAY' ||
987: scalar(@{$res->{'parts'}}) < 1) {
988: next;
989: }
1.54 matthew 990: if (scalar(@{$res->{'parts'}}) > 1) {
991: foreach my $part (@{$res->{'parts'}}) {
1.86 matthew 992: $excel_sheet->write($resource_name_row,
1.54 matthew 993: $cols_output++,
1.85 matthew 994: $res->{'title'}.' part '.$part,
995: $format->{'bold'});
1.54 matthew 996: }
997: } else {
1.86 matthew 998: $excel_sheet->write($resource_name_row,
1.54 matthew 999: $cols_output++,
1.85 matthew 1000: $res->{'title'},$format->{'bold'});
1.54 matthew 1001: }
1.82 matthew 1002: $count++;
1.54 matthew 1003: }
1.79 matthew 1004: }
1.87 ! matthew 1005: # Determine ending cell
! 1006: if ($count <= 1) {
! 1007: $seq->{'Excel:endcell'} = $seq->{'Excel:startcell'};
! 1008: $seq->{'Excel:endcol'} = $seq->{'Excel:startcol'};
! 1009: } else {
! 1010: $seq->{'Excel:endcell'} =
! 1011: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
! 1012: ($maximum_data_row,$cols_output-1);
! 1013: $seq->{'Excel:endcol'} = $cols_output-1;
! 1014: }
! 1015: # Create the formula for summing up this sequence
! 1016: if (! exists($seq->{'Excel:endcell'}) ||
! 1017: ! defined($seq->{'Excel:endcell'})) {
! 1018: $seq->{'Excel:endcell'} = $seq->{'Excel:startcell'};
! 1019: }
! 1020: $seq->{'Excel:sum'}= $excel_sheet->store_formula
! 1021: ('=SUM('.$seq->{'Excel:startcell'}.
! 1022: ':'.$seq->{'Excel:endcell'}.')');
1.79 matthew 1023: # Determine cell the score is held in
1024: $seq->{'Excel:scorecell'} =
1025: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
1.86 matthew 1026: ($maximum_data_row,$cols_output);
1.79 matthew 1027: $seq->{'Excel:scorecol'}=$cols_output;
1.87 ! matthew 1028: if ($chosen_output->{'base'} eq 'parts correct total') {
1.86 matthew 1029: $excel_sheet->write($resource_name_row,$cols_output++,
1030: 'parts correct',
1.85 matthew 1031: $format->{'bold'});
1.87 ! matthew 1032: } elsif ($chosen_output->{'sequence_sum'}) {
! 1033: if ($chosen_output->{'correct'}) {
! 1034: # Only reporting the number correct, so do not call it score
! 1035: $excel_sheet->write($resource_name_row,$cols_output++,
! 1036: 'sum',
! 1037: $format->{'bold'});
! 1038: } else {
! 1039: $excel_sheet->write($resource_name_row,$cols_output++,
! 1040: 'score',
! 1041: $format->{'bold'});
! 1042: }
1.32 matthew 1043: }
1.79 matthew 1044: #
1045: $total_formula_string.='+'.
1046: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
1.86 matthew 1047: ($maximum_data_row,$cols_output-1);
1.87 ! matthew 1048: if ($chosen_output->{'sequence_max'}) {
! 1049: $excel_sheet->write($resource_name_row,$cols_output++,
! 1050: 'maximum',
! 1051: $format->{'bold'});
! 1052: }
! 1053: }
! 1054: if ($chosen_output->{'grand_total'}) {
! 1055: $excel_sheet->write($resource_name_row,$cols_output++,'Total',
1.85 matthew 1056: $format->{'bold'});
1.32 matthew 1057: }
1.76 matthew 1058: $total_formula = $excel_sheet->store_formula($total_formula_string);
1.32 matthew 1059: #
1.87 ! matthew 1060: # Output a row for MAX, if appropriate
! 1061: if ($chosen_output->{'scores'}) {
! 1062: $cols_output = 0;
! 1063: foreach my $field (&get_student_fields_to_show()) {
! 1064: if ($field eq 'username' || $field eq 'fullname' ||
! 1065: $field eq 'id') {
! 1066: $excel_sheet->write($maximum_data_row,$cols_output++,'Maximum',
! 1067: $format->{'bold'});
! 1068: } else {
! 1069: $excel_sheet->write($maximum_data_row,$cols_output++,'');
! 1070: }
1.54 matthew 1071: }
1.87 ! matthew 1072: #
! 1073: # Add the maximums for each sequence or assessment
! 1074: my %total_cell_translation;
! 1075: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
! 1076: $cols_output=$seq->{'Excel:startcol'};
! 1077: $total_cell_translation{$seq->{'Excel:scorecell'}} =
! 1078: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
! 1079: ($maximum_data_row,$seq->{'Excel:scorecol'});
! 1080: my $weight;
! 1081: my $max = 0;
! 1082: foreach my $resource (@{$seq->{'contents'}}) {
! 1083: next if ($resource->{'type'} ne 'assessment');
! 1084: foreach my $part (@{$resource->{'parts'}}) {
! 1085: $weight = 1;
! 1086: if ($chosen_output->{'scores'}) {
! 1087: $weight = &Apache::lonnet::EXT
! 1088: ('resource.'.$part.'.weight',$resource->{'symb'},
! 1089: undef,undef,undef);
! 1090: if (!defined($weight) || ($weight eq '')) {
! 1091: $weight=1;
! 1092: }
! 1093: }
! 1094: if ($chosen_output->{'scores'} &&
! 1095: $chosen_output->{'every_problem'}) {
! 1096: $excel_sheet->write($maximum_data_row,$cols_output++,
! 1097: $weight);
1.54 matthew 1098: }
1.87 ! matthew 1099: $max += $weight;
1.54 matthew 1100: }
1.87 ! matthew 1101: }
! 1102: #
! 1103: if ($chosen_output->{'sequence_sum'} &&
! 1104: $chosen_output->{'every_problem'}) {
! 1105: my %replaceCells;
! 1106: $replaceCells{$seq->{'Excel:startcell'}} =
! 1107: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
! 1108: ($maximum_data_row,$seq->{'Excel:startcol'});
! 1109: $replaceCells{$seq->{'Excel:endcell'}} =
! 1110: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
! 1111: ($maximum_data_row,$seq->{'Excel:endcol'});
! 1112: $excel_sheet->repeat_formula($maximum_data_row,$cols_output++,
! 1113: $seq->{'Excel:sum'},undef,
! 1114: %replaceCells);
! 1115: } elsif ($chosen_output->{'sequence_sum'}) {
! 1116: $excel_sheet->write($maximum_data_row,$cols_output++,$max);
! 1117: }
! 1118: if ($chosen_output->{'sequence_max'}) {
! 1119: $excel_sheet->write($maximum_data_row,$cols_output++,$max);
1.45 matthew 1120: }
1.87 ! matthew 1121: #
1.45 matthew 1122: }
1.87 ! matthew 1123: if ($chosen_output->{'grand_total'}) {
1.86 matthew 1124: $excel_sheet->repeat_formula($maximum_data_row,$cols_output++,
1.87 ! matthew 1125: $total_formula,undef,
! 1126: %total_cell_translation);
1.79 matthew 1127: }
1.87 ! matthew 1128: } # End of MAXIMUM row output if ($chosen_output->{'scores'}) {
1.86 matthew 1129: $rows_output = $first_data_row;
1.32 matthew 1130: #
1131: # Let the user know what we are doing
1132: my $studentcount = scalar(@Apache::lonstatistics::Students);
1.85 matthew 1133: if ($ENV{'form.SelectedStudent'}) {
1134: $studentcount = '1';
1135: }
1136: if ($studentcount > 1) {
1137: $r->print('<h1>'.&mt('Compiling Excel spreadsheet for [_1] students',
1138: $studentcount)."</h1>\n");
1139: } else {
1140: $r->print('<h1>'.
1141: &mt('Compiling Excel spreadsheet for 1 student').
1142: "</h1>\n");
1143: }
1.32 matthew 1144: $r->rflush();
1.31 matthew 1145: #
1.36 matthew 1146: # Initialize progress window
1147: %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
1148: ($r,'Excel File Compilation Status',
1149: 'Excel File Compilation Progress', $studentcount);
1150: #
1.62 matthew 1151: &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,
1152: 'Processing first student');
1.31 matthew 1153: return;
1154: }
1155:
1156: sub excel_outputstudent {
1157: my ($r,$student) = @_;
1.54 matthew 1158: return if ($request_aborted);
1.32 matthew 1159: return if (! defined($excel_sheet));
1160: $cols_output=0;
1161: #
1162: # Write out student data
1163: my @to_show = &get_student_fields_to_show();
1164: foreach my $field (@to_show) {
1165: $excel_sheet->write($rows_output,$cols_output++,$student->{$field});
1166: }
1167: #
1168: # Get student assessment data
1169: my %StudentsData;
1170: my @tmp = &Apache::loncoursedata::get_current_state($student->{'username'},
1171: $student->{'domain'},
1172: undef,
1173: $ENV{'request.course.id'});
1174: if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) {
1175: %StudentsData = @tmp;
1176: }
1177: #
1178: # Write out sequence scores and totals data
1.76 matthew 1179: my %total_cell_translation;
1.40 matthew 1180: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
1.85 matthew 1181: $cols_output = $seq->{'Excel:startcol'};
1.76 matthew 1182: # Keep track of cells to translate in total cell
1183: $total_cell_translation{$seq->{'Excel:scorecell'}} =
1184: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
1185: ($rows_output,$seq->{'Excel:scorecol'});
1186: #
1.54 matthew 1187: my ($performance,$performance_length,$score,$seq_max,$rawdata);
1.87 ! matthew 1188: if ($chosen_output->{'tries'} || $chosen_output->{'correct'}){
1.54 matthew 1189: ($performance,$performance_length,$score,$seq_max,$rawdata) =
1190: &StudentTriesOnSequence($student,\%StudentsData,
1191: $seq,'no');
1192: } else {
1193: ($performance,$performance_length,$score,$seq_max,$rawdata) =
1194: &StudentPerformanceOnSequence($student,\%StudentsData,
1195: $seq,'no');
1.87 ! matthew 1196: }
! 1197: if ($chosen_output->{'every_problem'}) {
! 1198: if ($chosen_output->{'correct'}) {
! 1199: # only indiciate if each item is correct or not
! 1200: foreach my $value (@$rawdata) {
! 1201: # nonzero means correct
! 1202: $value = 1 if ($value > 0);
! 1203: $excel_sheet->write($rows_output,$cols_output++,$value);
! 1204: }
! 1205: } else {
! 1206: foreach my $value (@$rawdata) {
! 1207: $excel_sheet->write($rows_output,$cols_output++,$value);
! 1208: }
! 1209: }
1.54 matthew 1210: }
1.87 ! matthew 1211: if ($chosen_output->{'sequence_sum'} &&
! 1212: $chosen_output->{'every_problem'}) {
1.76 matthew 1213: # Write a formula for the sum of this sequence
1214: my %replaceCells;
1215: $replaceCells{$seq->{'Excel:startcell'}} =
1216: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
1217: ($rows_output,$seq->{'Excel:startcol'});
1218: $replaceCells{$seq->{'Excel:endcell'}} =
1219: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
1220: ($rows_output,$seq->{'Excel:endcol'});
1221: # The undef is for the format
1.81 matthew 1222: if (scalar(keys(%replaceCells)) == 1) {
1223: $excel_sheet->repeat_formula($rows_output,$cols_output++,
1224: $seq->{'Excel:sum'},undef,
1225: %replaceCells,%replaceCells);
1226: } else {
1227: $excel_sheet->repeat_formula($rows_output,$cols_output++,
1228: $seq->{'Excel:sum'},undef,
1229: %replaceCells);
1230: }
1.87 ! matthew 1231: } elsif ($chosen_output->{'sequence_sum'}) {
1.32 matthew 1232: $excel_sheet->write($rows_output,$cols_output++,$score);
1233: }
1.87 ! matthew 1234: if ($chosen_output->{'sequence_max'}) {
1.32 matthew 1235: $excel_sheet->write($rows_output,$cols_output++,$seq_max);
1236: }
1237: }
1.76 matthew 1238: #
1.87 ! matthew 1239: if ($chosen_output->{'grand_total'}) {
! 1240: $excel_sheet->repeat_formula($rows_output,$cols_output++,
! 1241: $total_formula,undef,
! 1242: %total_cell_translation);
! 1243: }
1.32 matthew 1244: #
1245: # Bookkeeping
1246: $rows_output++;
1247: $cols_output=0;
1248: #
1.36 matthew 1249: # Update the progress window
1250: &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student');
1.32 matthew 1251: return;
1.31 matthew 1252: }
1253:
1254: sub excel_finish {
1255: my ($r) = @_;
1.54 matthew 1256: return if ($request_aborted);
1.32 matthew 1257: return if (! defined($excel_sheet));
1258: #
1259: # Write the excel file
1260: $excel_workbook->close();
1261: my $c = $r->connection();
1262: #
1263: return if($c->aborted());
1264: #
1.36 matthew 1265: # Close the progress window
1266: &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
1267: #
1.32 matthew 1268: # Tell the user where to get their excel file
1.36 matthew 1269: $r->print('<br />'.
1.32 matthew 1270: '<a href="'.$filename.'">Your Excel spreadsheet.</a>'."\n");
1271: $r->rflush();
1272: return;
1.31 matthew 1273: }
1274:
1275: }
1.30 matthew 1276: #######################################################
1277: #######################################################
1278:
1279: =pod
1280:
1.31 matthew 1281: =head2 CSV output routines
1282:
1283: =item &csv_initialize($r)
1284:
1285: =item &csv_outputstudent($r,$student)
1286:
1287: =item &csv_finish($r)
1.30 matthew 1288:
1289: =cut
1290:
1291: #######################################################
1292: #######################################################
1.31 matthew 1293: {
1294:
1.37 matthew 1295: my $outputfile;
1296: my $filename;
1.54 matthew 1297: my $request_aborted;
1.37 matthew 1298: my %prog_state; # progress window state
1299:
1.31 matthew 1300: sub csv_initialize{
1301: my ($r) = @_;
1.37 matthew 1302: #
1303: # Clean up
1.66 matthew 1304: undef($outputfile);
1305: undef($filename);
1306: undef($request_aborted);
1.37 matthew 1307: undef(%prog_state);
1308: #
1.54 matthew 1309: # Deal with unimplemented requests
1310: $request_aborted = undef;
1.87 ! matthew 1311: if ($chosen_output->{'base'} =~ /final table/) {
1.54 matthew 1312: $r->print(<<END);
1313: <h2>Unable to Complete Request</h2>
1314: <p>
1315: The <b>Summary Table (Scores)</b> option is not available for non-HTML output.
1316: </p>
1317: END
1318: $request_aborted = 1;
1319: }
1320: return if ($request_aborted);
1.87 ! matthew 1321: #
! 1322: # Initialize progress window
! 1323: my $studentcount = scalar(@Apache::lonstatistics::Students);
! 1324: %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
! 1325: ($r,'CSV File Compilation Status',
! 1326: 'CSV File Compilation Progress', $studentcount);
1.54 matthew 1327: #
1.37 matthew 1328: # Open a file
1329: $filename = '/prtspool/'.
1330: $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
1331: time.'_'.rand(1000000000).'.csv';
1332: unless ($outputfile = Apache::File->new('>/home/httpd'.$filename)) {
1333: $r->log_error("Couldn't open $filename for output $!");
1334: $r->print("Problems occured in writing the csv file. ".
1335: "This error has been logged. ".
1336: "Please alert your LON-CAPA administrator.");
1337: $outputfile = undef;
1338: }
1.38 matthew 1339: #
1340: # Datestamp
1341: my $description = $ENV{'course.'.$ENV{'request.course.id'}.'.description'};
1342: print $outputfile '"'.&Apache::loncommon::csv_translate($description).'",'.
1343: '"'.&Apache::loncommon::csv_translate(scalar(localtime(time))).'"'.
1344: "\n";
1.37 matthew 1345: #
1346: # Print out the headings
1.87 ! matthew 1347: my $sequence_row = '';
! 1348: my $resource_row = undef;
1.37 matthew 1349: foreach my $field (&get_student_fields_to_show()) {
1.87 ! matthew 1350: $sequence_row .='"",';
! 1351: $resource_row .= '"'.&Apache::loncommon::csv_translate($field).'",';
1.37 matthew 1352: }
1.40 matthew 1353: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
1.87 ! matthew 1354: $sequence_row .= '"'.
! 1355: &Apache::loncommon::csv_translate($seq->{'title'}).'",';
! 1356: my $count = 0;
! 1357: if ($chosen_output->{'every_problem'}) {
1.53 matthew 1358: foreach my $res (@{$seq->{'contents'}}) {
1.87 ! matthew 1359: if ($res->{'type'} ne 'assessment' ||
! 1360: ! exists($res->{'parts'}) ||
! 1361: ref($res->{'parts'}) ne 'ARRAY' ||
! 1362: scalar(@{$res->{'parts'}}) < 1) {
! 1363: next;
! 1364: }
1.53 matthew 1365: foreach my $part (@{$res->{'parts'}}) {
1.87 ! matthew 1366: $resource_row .= '"'.
! 1367: &Apache::loncommon::csv_translate($res->{'title'}.
! 1368: ', Part '.$part
! 1369: ).'",';
! 1370: $count++;
1.53 matthew 1371: }
1372: }
1.37 matthew 1373: }
1.87 ! matthew 1374: $sequence_row.='"",'x$count;
! 1375: if ($chosen_output->{'sequence_sum'}) {
! 1376: if($chosen_output->{'correct'}) {
! 1377: $resource_row .= '"sum",';
! 1378: } else {
! 1379: $resource_row .= '"score",';
! 1380: }
! 1381: }
! 1382: if ($chosen_output->{'sequence_max'}) {
! 1383: $sequence_row.= '"",';
! 1384: $resource_row .= '"maximum possible",';
! 1385: }
1.37 matthew 1386: }
1.87 ! matthew 1387: if ($chosen_output->{'grand_total'}) {
! 1388: $sequence_row.= '"",';
! 1389: $resource_row.= '"Total",';
! 1390: }
! 1391: chomp($sequence_row);
! 1392: chomp($resource_row);
! 1393: print $outputfile $sequence_row."\n";
! 1394: print $outputfile $resource_row."\n";
1.31 matthew 1395: return;
1396: }
1397:
1398: sub csv_outputstudent {
1399: my ($r,$student) = @_;
1.54 matthew 1400: return if ($request_aborted);
1.37 matthew 1401: return if (! defined($outputfile));
1402: my $Str = '';
1403: #
1404: # Output student fields
1405: my @to_show = &get_student_fields_to_show();
1406: foreach my $field (@to_show) {
1407: $Str .= '"'.&Apache::loncommon::csv_translate($student->{$field}).'",';
1408: }
1409: #
1410: # Get student assessment data
1411: my %StudentsData;
1412: my @tmp = &Apache::loncoursedata::get_current_state($student->{'username'},
1413: $student->{'domain'},
1414: undef,
1415: $ENV{'request.course.id'});
1416: if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) {
1417: %StudentsData = @tmp;
1418: }
1419: #
1420: # Output performance data
1.87 ! matthew 1421: my $total = 0;
1.40 matthew 1422: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
1.54 matthew 1423: my ($performance,$performance_length,$score,$seq_max,$rawdata);
1.87 ! matthew 1424: if ($chosen_output->{'tries'}){
1.54 matthew 1425: ($performance,$performance_length,$score,$seq_max,$rawdata) =
1426: &StudentTriesOnSequence($student,\%StudentsData,
1427: $seq,'no');
1428: } else {
1429: ($performance,$performance_length,$score,$seq_max,$rawdata) =
1430: &StudentPerformanceOnSequence($student,\%StudentsData,
1431: $seq,'no');
1432: }
1.87 ! matthew 1433: if ($chosen_output->{'every_problem'}) {
! 1434: if ($chosen_output->{'correct'}) {
! 1435: $score = 0;
! 1436: # Deal with number of parts correct data
! 1437: $Str .= '"'.join('","',( map { if ($_>0) {
! 1438: $score += 1;
! 1439: 1;
! 1440: } else {
! 1441: 0;
! 1442: }
! 1443: } @$rawdata)).'",';
! 1444: } else {
! 1445: $Str .= '"'.join('","',(@$rawdata)).'",';
! 1446: }
! 1447: }
! 1448: if ($chosen_output->{'sequence_sum'}) {
1.37 matthew 1449: $Str .= '"'.$score.'",';
1.87 ! matthew 1450: }
! 1451: if ($chosen_output->{'sequence_max'}) {
! 1452: $Str .= '"'.$seq_max.'",';
1.37 matthew 1453: }
1.87 ! matthew 1454: $total+=$score;
! 1455: }
! 1456: if ($chosen_output->{'grand_total'}) {
! 1457: $Str .= '"'.$total.'",';
1.37 matthew 1458: }
1459: chop($Str);
1460: $Str .= "\n";
1461: print $outputfile $Str;
1462: #
1463: # Update the progress window
1464: &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student');
1465: return;
1.31 matthew 1466: }
1467:
1468: sub csv_finish {
1469: my ($r) = @_;
1.54 matthew 1470: return if ($request_aborted);
1.37 matthew 1471: return if (! defined($outputfile));
1472: close($outputfile);
1473: #
1474: my $c = $r->connection();
1475: return if ($c->aborted());
1476: #
1477: # Close the progress window
1478: &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
1479: #
1480: # Tell the user where to get their csv file
1481: $r->print('<br />'.
1482: '<a href="'.$filename.'">Your csv file.</a>'."\n");
1483: $r->rflush();
1484: return;
1485:
1.31 matthew 1486: }
1.2 stredwic 1487:
1488: }
1489:
1.28 matthew 1490: #######################################################
1491: #######################################################
1492:
1.2 stredwic 1493: =pod
1494:
1.54 matthew 1495: =item &StudentTriesOnSequence()
1.2 stredwic 1496:
1.30 matthew 1497: Inputs:
1.2 stredwic 1498:
1499: =over 4
1500:
1.30 matthew 1501: =item $student
1.28 matthew 1502:
1.30 matthew 1503: =item $studentdata Hash ref to all student data
1.2 stredwic 1504:
1.30 matthew 1505: =item $seq Hash ref, the sequence we are working on
1.2 stredwic 1506:
1.30 matthew 1507: =item $links if defined we will output links to each resource.
1.2 stredwic 1508:
1.28 matthew 1509: =back
1.2 stredwic 1510:
1511: =cut
1.1 stredwic 1512:
1.28 matthew 1513: #######################################################
1514: #######################################################
1.54 matthew 1515: sub StudentTriesOnSequence {
1.32 matthew 1516: my ($student,$studentdata,$seq,$links) = @_;
1.31 matthew 1517: $links = 'no' if (! defined($links));
1.1 stredwic 1518: my $Str = '';
1.30 matthew 1519: my ($sum,$max) = (0,0);
1.51 matthew 1520: my $performance_length = 0;
1.54 matthew 1521: my @TriesData = ();
1522: my $tries;
1.30 matthew 1523: foreach my $resource (@{$seq->{'contents'}}) {
1524: next if ($resource->{'type'} ne 'assessment');
1525: my $resource_data = $studentdata->{$resource->{'symb'}};
1526: my $value = '';
1527: foreach my $partnum (@{$resource->{'parts'}}) {
1.54 matthew 1528: $tries = undef;
1.30 matthew 1529: $max++;
1.51 matthew 1530: $performance_length++;
1.30 matthew 1531: my $symbol = ' '; # default to space
1532: #
1.78 matthew 1533: my $awarded = 0;
1534: if (exists($resource_data->{'resource.'.$partnum.'.awarded'})) {
1535: $awarded = $resource_data->{'resource.'.$partnum.'.awarded'};
1536: $awarded = 0 if (! $awarded);
1537: }
1538: #
1539: my $status = '';
1.30 matthew 1540: if (exists($resource_data->{'resource.'.$partnum.'.solved'})) {
1.78 matthew 1541: $status = $resource_data->{'resource.'.$partnum.'.solved'};
1542: }
1543: #
1544: my $tries = 0;
1545: if(exists($resource_data->{'resource.'.$partnum.'.tries'})) {
1546: $tries = $resource_data->{'resource.'.$partnum.'.tries'};
1547: }
1548: #
1549: if ($awarded > 0) {
1550: # The student has gotten the problem correct to some degree
1551: if ($status eq 'excused') {
1552: $symbol = 'x';
1553: $max--;
1554: } elsif ($status eq 'correct_by_override') {
1.30 matthew 1555: $symbol = '+';
1556: $sum++;
1.78 matthew 1557: } elsif ($tries > 0) {
1.54 matthew 1558: if ($tries > 9) {
1.30 matthew 1559: $symbol = '*';
1.78 matthew 1560: } else {
1.54 matthew 1561: $symbol = $tries;
1.30 matthew 1562: }
1563: $sum++;
1564: } else {
1.78 matthew 1565: $symbol = '+';
1566: $sum++;
1.2 stredwic 1567: }
1.30 matthew 1568: } else {
1.78 matthew 1569: # The student has the problem incorrect or it is ungraded
1570: if ($status eq 'excused') {
1571: $symbol = 'x';
1572: $max--;
1573: } elsif ($status eq 'incorrect_by_override') {
1574: $symbol = '-';
1575: } elsif ($status eq 'ungraded_attempted') {
1576: $symbol = '#';
1577: } elsif ($status eq 'incorrect_attempted' ||
1578: $tries > 0) {
1.30 matthew 1579: $symbol = '.';
1580: } else {
1.78 matthew 1581: # Problem is wrong and has not been attempted.
1582: $symbol=' ';
1.18 matthew 1583: }
1.2 stredwic 1584: }
1.54 matthew 1585: #
1586: if (! defined($tries)) {
1587: $tries = $symbol;
1588: }
1589: push (@TriesData,$tries);
1.30 matthew 1590: #
1.47 matthew 1591: if ( ($links eq 'yes' && $symbol ne ' ') ||
1592: ($links eq 'all')) {
1.49 matthew 1593: if (length($symbol) > 1) {
1594: &Apache::lonnet::logthis('length of symbol "'.$symbol.'" > 1');
1595: }
1.30 matthew 1596: $symbol = '<a href="/adm/grades'.
1597: '?symb='.&Apache::lonnet::escape($resource->{'symb'}).
1598: '&student='.$student->{'username'}.
1.64 albertel 1599: '&userdom='.$student->{'domain'}.
1.30 matthew 1600: '&command=submission">'.$symbol.'</a>';
1601: }
1602: $value .= $symbol;
1.2 stredwic 1603: }
1.30 matthew 1604: $Str .= $value;
1.17 minaeibi 1605: }
1.51 matthew 1606: if ($seq->{'randompick'}) {
1607: $max = $seq->{'randompick'};
1608: }
1.54 matthew 1609: return ($Str,$performance_length,$sum,$max,\@TriesData);
1610: }
1611:
1612: #######################################################
1613: #######################################################
1614:
1615: =pod
1616:
1617: =item &StudentPerformanceOnSequence()
1618:
1619: Inputs:
1620:
1621: =over 4
1622:
1623: =item $student
1624:
1625: =item $studentdata Hash ref to all student data
1626:
1627: =item $seq Hash ref, the sequence we are working on
1628:
1629: =item $links if defined we will output links to each resource.
1630:
1631: =back
1632:
1633: =cut
1634:
1635: #######################################################
1636: #######################################################
1637: sub StudentPerformanceOnSequence {
1638: my ($student,$studentdata,$seq,$links) = @_;
1639: $links = 'no' if (! defined($links));
1640: my $Str = ''; # final result string
1641: my ($score,$max) = (0,0);
1642: my $performance_length = 0;
1643: my $symbol;
1644: my @ScoreData = ();
1645: my $partscore;
1646: foreach my $resource (@{$seq->{'contents'}}) {
1647: next if ($resource->{'type'} ne 'assessment');
1648: my $resource_data = $studentdata->{$resource->{'symb'}};
1649: foreach my $part (@{$resource->{'parts'}}) {
1650: $partscore = undef;
1651: my $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight',
1652: $resource->{'symb'},
1653: $student->{'domain'},
1654: $student->{'username'},
1655: $student->{'section'});
1656: if (!defined($weight) || ($weight eq '')) {
1657: $weight=1;
1658: }
1659: #
1660: $max += $weight; # see the 'excused' branch below...
1661: $performance_length++; # one character per part
1662: $symbol = ' '; # default to space
1663: #
1664: my $awarded = 0;
1665: if (exists($resource_data->{'resource.'.$part.'.awarded'})) {
1666: $awarded = $resource_data->{'resource.'.$part.'.awarded'};
1.67 matthew 1667: $awarded = 0 if (! $awarded);
1.54 matthew 1668: }
1669: #
1670: $partscore = $weight*$awarded;
1671: $score += $partscore;
1.63 matthew 1672: $symbol = $partscore;
1.70 matthew 1673: if (abs($symbol - sprintf("%.0f",$symbol)) < 0.001) {
1674: $symbol = sprintf("%.0f",$symbol);
1675: }
1.54 matthew 1676: if (length($symbol) > 1) {
1677: $symbol = '*';
1678: }
1679: if (exists($resource_data->{'resource.'.$part.'.solved'})) {
1680: my $status = $resource_data->{'resource.'.$part.'.solved'};
1681: if ($status eq 'excused') {
1682: $symbol = 'x';
1683: $max -= $weight; # Do not count 'excused' problems.
1684: }
1685: } else {
1686: # Unsolved. Did they try?
1687: if (exists($resource_data->{'resource.'.$part.'.tries'})){
1688: $symbol = '.';
1689: } else {
1690: $symbol = ' ';
1691: }
1692: }
1693: #
1.60 matthew 1694: if (! defined($partscore)) {
1695: $partscore = $symbol;
1696: }
1697: push (@ScoreData,$partscore);
1698: #
1.54 matthew 1699: if ( ($links eq 'yes' && $symbol ne ' ') || ($links eq 'all')) {
1700: $symbol = '<a href="/adm/grades'.
1701: '?symb='.&Apache::lonnet::escape($resource->{'symb'}).
1702: '&student='.$student->{'username'}.
1.64 albertel 1703: '&userdom='.$student->{'domain'}.
1.54 matthew 1704: '&command=submission">'.$symbol.'</a>';
1705: }
1.60 matthew 1706: $Str .= $symbol;
1.54 matthew 1707: }
1708: }
1709: return ($Str,$performance_length,$score,$max,\@ScoreData);
1.1 stredwic 1710: }
1.23 minaeibi 1711:
1.28 matthew 1712: #######################################################
1713: #######################################################
1.23 minaeibi 1714:
1.2 stredwic 1715: =pod
1716:
1717: =item &CreateLegend()
1718:
1719: This function returns a formatted string containing the legend for the
1720: chart. The legend describes the symbols used to represent grades for
1721: problems.
1722:
1723: =cut
1724:
1.28 matthew 1725: #######################################################
1726: #######################################################
1.2 stredwic 1727: sub CreateLegend {
1728: my $Str = "<p><pre>".
1.13 minaeibi 1729: " 1 correct by student in 1 try\n".
1730: " 7 correct by student in 7 tries\n".
1.12 minaeibi 1731: " * correct by student in more than 9 tries\n".
1.20 minaeibi 1732: " + correct by hand grading or override\n".
1.12 minaeibi 1733: " - incorrect by override\n".
1734: " . incorrect attempted\n".
1735: " # ungraded attempted\n".
1.13 minaeibi 1736: " not attempted (blank field)\n".
1.12 minaeibi 1737: " x excused".
1.17 minaeibi 1738: "</pre><p>";
1.2 stredwic 1739: return $Str;
1740: }
1741:
1.28 matthew 1742: #######################################################
1743: #######################################################
1744:
1.30 matthew 1745: =pod
1.2 stredwic 1746:
1747: =back
1748:
1749: =cut
1750:
1.28 matthew 1751: #######################################################
1752: #######################################################
1.2 stredwic 1753:
1.28 matthew 1754: 1;
1.2 stredwic 1755:
1.1 stredwic 1756: __END__
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>