package Gradebook; use strict; use warnings; sub new { my $proto = shift; my $self = {}; my $class = ref($proto) || $proto; $self->{index} = 0; bless ($self, $class); } sub parse { my ($self, $dump) = @_; chomp $dump; $dump = reverse $dump; chomp $dump; $dump = reverse $dump; my @dump = split (/\n/, $dump); # determine the amount of sections in the script. In a 1 pager, it will be 3, 2 pages will be 5, 3 will be 7... my @sections; my $q=0; foreach my $line (@dump) { if ($line =~ /^\-+/i) { $q++; next; } $sections[$q] .= $line."\n"; $q++ if ($line =~ /^Possible :/i); } $q = ($q/2); # get the title chomp (@dump); my $firstline = 0; do { $firstline = shift @dump } while ( $firstline !~ /[a-z]+/ ); $self->{name} = $firstline."\n".(shift @dump)."\n".(shift @dump); shift @dump; # the length of the longest possible record my @fields = split (/\s+/, shift @dump); shift @dump; my ($length) = $dump[0] =~ /^(.*?\s.*?\s+)[^\s]/; $length = length($length)-3; # grabs the records out of the sections my @names; my @student_info; for (my $i=1; $i<@sections; $i+=2) { push @student_info, join("\n",grep{$_}map{ my $l = ($length 6 ) ? substr($password, 0, 6) : $password; $password =~ tr/.,!@#$%^&*(){}[]/q/; $student->{$name} = { firstname => $firstname, lastname => $lastname, password => $password, grade => $grade, total => $total, percent => $percent, assignment => {%assignments}, catagory => {%catagories}, } } my @info = @student_info[$loc-1 .. $#student_info]; my @averages = split (/\s+/, shift @info); shift @averages; shift @averages; my %a_averages = (); for my $i (0 .. $noa) { my $average = shift @averages; $a_averages{$i+1} = $average; } my $total_average = shift @averages; my $pct_average = shift @averages; my %c_averages = (); for my $i (0..@averages) { $c_averages{$i+1} = $averages[$i]; } my %averages = ( assignment => {%a_averages}, catagory => {%c_averages}, ); my @possible = split (/\s+/, shift @info); shift @possible; shift @possible; my %a_possible = (); for my $i (0 .. $noa) { my $possible = shift @possible; $a_possible{$i+1} = $possible; } my $total_possible = shift @possible; my %c_possible = (); for my $i (0..@possible) { $c_possible{$i+1} = $possible[$i]; } my %possible = ( assignment => {%a_possible}, catagory => {%c_possible}, ); my @detailed; foreach my $section (@sections) { $section .= $firstline; ($section) = $section =~ /^(.*?)\Q$firstline\E/si; } @info = (); foreach my $section (@sections) { my @entry = split (/\n/, $section); push (@info, @entry); } @info = grep{$_}@info; @info = do{my%h;grep{!$h{$_}++}@info}; @info = grep{$_ !~ /\Q"( )" Indicates the assignment was not submitted.\E/i}@info; @info = grep{$_ !~ /\Q"ex" Indicates student exempted from entry.\E/i}@info; $noa++; my @asses; shift @info; # shift @info; while (my $entry = shift @info) { chomp $entry; last if ($entry eq ''); last if ($entry eq "\n"); last if ($entry eq "\r"); last if ($entry eq "\r\n"); last if ($entry eq "\n\r"); last if ($entry =~ /^Category/i); $entry =~ s/(\d+\))/ $1/g; $entry =~ tr/(/{/; $entry =~ s/(?:(\{[^)]*)(?{student} = $student; $self->{average} = {%averages}; $self->{assignment} = [@assignment_names]; $self->{catagory} = [@catagory_names]; $self->{order} = [@order]; $self->{possible} = {%possible}; $self->{class_average} = $pct_average; $self->{total_average} = $total_average; $self->{total_possible} = $total_possible; } sub report_name { my $self = shift; return (join "\n", map {s/\s{2,}//; $_} split (/\n/, $self->{name})); } sub list_students { my $self = shift; my @return_array; foreach my $name (keys(%{$self->{student}})) { my $lastname = $self->{student}->{$name}->{lastname}; my $firstname = $self->{student}->{$name}->{firstname}; my $password = $self->{student}->{$name}->{password}; push (@return_array, {first=>$firstname, last=>$lastname, pw=>$password}); } return @return_array; } sub next_student { my $self = shift; my $returnv = ${$self->{order}}[$self->{index}]; $self->{index}++; $self->{index} = 0 if ($self->{index} > $#{$self->{order}}); return $returnv; } sub average_percent { my $self = shift; return $self->{class_average}; } sub average_points { my $self = shift; return $self->{total_average}; } sub possible_points { my $self = shift; return $self->{total_possible}; } sub assignment_names { my $self = shift; return @{$self->{assignment}} } sub assignment_possible { my ($self, $name) = @_; my $index = get_index($name, @{$self->{assignment}}); return $self->{possible}->{assignment}->{$index}; } sub assignment_scores { my ($self, $name) = @_; my @return_array; my $index = get_index ($name, @{$self->{assignment}}); foreach my $student (@{$self->{order}}) { my $score = $self->{student}->{$student}->{assignment}->{$index}; push (@return_array, $score); } return @return_array; } sub assignment_average { my ($self, $name) = @_; my $index = get_index($name, @{$self->{assignment}}); return $self->{average}->{assignment}->{$index}; } sub catagory_names { my $self = shift; return @{$self->{catagory}} } sub catagory_possible { my ($self, $name) = @_; my $index = get_index ($name, @{$self->{catagory}}); return $self->{possible}->{catagory}->{$index}; } sub catagory_scores { my ($self, $name) = @_; my @return_array; my $index = get_index ($name, @{$self->{catagory}}); foreach my $student (@{$self->{order}}) { my $score = $self->{student}->{$student}->{catagory}->{$index}; push (@return_array, $score); } return @return_array; } sub catagory_average { my ($self, $name) = @_; my $index = get_index($name, @{$self->{catagory}}); return $self->{average}->{catagory}->{$index}; } sub student_assignments { my ($self, $first, $last) = @_; my $student = $first."_".$last; my @return_value; my @keys; while ( my($key,$value) = each(%{$self->{student}->{$student}->{assignment}}) ) { push (@keys, $key); push (@return_value, $value); } my @order; foreach my $assignment (@{$self->{assignment}}) { for (my $i=0; $i<@keys; $i++) { my $index = get_index($assignment, @{$self->{assignment}}); push (@order, $i) if ($index == $keys[$i]); } } @return_value = @return_value[@order]; return @return_value; } sub student_catagories { my ($self, $first, $last) = @_; my $student = $first."_".$last; my @return_value; my @keys; while ( my($key,$value) = each (%{$self->{student}->{$student}->{catagory}}) ) { push (@keys, $key); push (@return_value, $value); } my @order; foreach my $catagory (@{$self->{catagory}}) { for (my $i=0; $i<@keys; $i++) { my $index = get_index($catagory, @{$self->{assignment}}); push (@order, $i) if ($index == $keys[$i]); } } @return_value = @return_value[@order]; return @return_value; } sub student_total_points { my ($self, $first, $last) = @_; my $student = $first."_".$last; return $self->{student}->{$student}->{total}; } sub student_percent { my ($self, $first, $last) = @_; my $student = $first."_".$last; return $self->{student}->{$student}->{percent}; } sub student_letter { my ($self, $first, $last) = @_; my $student = $first."_".$last; return $self->{student}->{$student}->{grade}; } # subroutines for use by the module sub get_index { my ($name, @fields) = @_; for (my $i=0; $i<@fields; $i++) { if ($fields[$i] eq $name) { return $i+1; } } } "JAPH"; =head1 NAME Gradebook =head1 SYNOPSIS use Gradebook; # create a new gradbook my $gradebook = new Gradebook; # parse $gradebook->parse($data); # do stuff with the methods =head1 DESCRIPTION Gradebook is a module useful for parsing Gradebook Plus text reports, a program used by many K-12 school districts. Simplely go to Reports->Gradebook of entire Class->(check fancy report, catagory totals, and all grades)->Save to disk. The parser can then parse the contents of this file. Use it how you like; I use it so teachers can put their grades online with minimal effort on their part. =head1 METHODS =over 4 =item parse($report) Send it a scalar with the report to parse, in one large chunk. The Object becomes filled upon using this method. =item report_name Returns the name of the report, if you happen want it. =item list_students Returns a list of students in the report. Each element is a reference to an anonymous hash with 3 elements: first last pw pw is a generated password which you can use, rather than create your own. Remember, this module was developed for use with an online grade report system. =item next_student A counter of the next student in the report. Kinda like an internal foreach loop. Returns the next student. =item average_percent Returns average total percent for the class =item average_points Returns average total points for the class =item total_points Returns total possible points for the class =item assignment_*($assignment_name) Returns * for the given assignment name The exception is assignment_names, which does not take an argument and returns a list of all assignment names. =item catagory_*($catagory_name) Returns * for the given catagory name The exception is catagory_names, which does not take an argument and returns a list of all catagory names. =item student_*($first, $last) Returns * for the given student. The exceptions are with student_assignments and student_catagories, which return an array of all scores in the same order as @assignment_names. =head1 AUTHOR Joseph F. Ryan, ryan.311@osu.edu =cut