#!/usr/bin/perl -w # converts RIT's SIS telnet format to a more useful XML format. use strict; use XML::Twig; use Getopt::Std; use Data::Schools; use Config::Tiny; ######## user spec'd config ########## # load the cfg file my $Conf = Config::Tiny->new(); $Conf = Config::Tiny->read("/etc/schedules.conf"); my $basedir = $Conf->{_}->{'basedir'}; my $dtd = "$basedir/sched.dtd"; my $schools_xml = "$basedir/schools.xml"; ####### load configs ########### getopts("puthr:s:"); our( $opt_h, $opt_u, $opt_p, $opt_r, $opt_s ); usage() if defined $opt_h; usage() unless @ARGV; unless($opt_s){ die "the -s parameter is required\n\nrun with no arguments to see the usage info\n"; } my $schools = new Data::Schools( $schools_xml ); my %opts; my %cattrs; $cattrs{'school'} = $opt_s; if( exists $cattrs{'school'}){ $opts{'name'} = $schools->term_at_date($cattrs{'school'}); } unless( $opts{'name'} ){ die "cannot find term name\n"; } my $update_xml = defined $opt_u; $opts{'name'} = $opt_r if defined $opt_r; my $sisfile = $ARGV[0]; my $file = $ARGV[1]; my $makenew = !defined $file || ! -e $file; my $palm = $opt_p; # the only time you don't want it writing to the disk is if there's already # data there. $update_xml = 1 if $makenew && defined $file; my $usexml = 1 unless $opt_p; ######## done loading configs ######## if( $palm ){ to_palm( $sisfile ); exit; } my $t = new XML::Twig( PrettyPrint => 'record', EmptyTags => 'normal', load_DTD => 0 ); my $cur_term; if( $makenew ){ $t->set_xml_version('1.0'); $t->set_doctype('courselist', $dtd); my $elt; $elt = new XML::Twig::Elt( 'courselist', \%cattrs); $t->set_root( $elt ); }else{ print STDERR "parsing $file.."; $t->parsefile( $file ); print STDERR "done\n"; } #get the current term node if( $cur_term = ($t->root()->get_xpath("term[\@name=\"$opts{'name'}\"]"))[0] ){ $cur_term->delete; # clear the node so we can put the updated data in it} } $cur_term = new XML::Twig::Elt( 'term', \%opts ); $cur_term->paste( 'first_child', $t->root() ); open SIS, "$sisfile"; my $pcnum = ""; my $open; my %catts; my @ccont; my $croom = ""; my $cbldg = ""; my $cloc = ""; my $cnum = ""; my $cname = ""; my $ccred = ""; my $cprof = ""; my $cdow = ""; my $start = ""; my $end = ""; while( my $line = ){ $line =~ s/\#.+$//; # hopefully this should catch everything. unlike other ones, it tries # to ignore broken columns and looks only for content it knows if( $line=~ /^\s*(?: (\d{4}\-\d{3}\-\d{2})\s+ (.{10,30})\s+ (\d)\s+ (.{2,13}) )? \s+([MTWRFSUBNA]{1,5}) \s*(TBA|Online|NA|\d{3,4}(?:AM|PM|N|M)) \s+(TBA|Course|NA|\d{3,4}(?:AM|PM|N|M)) \s+(\w{1,4}) (?:\s+(\w{1,4}))?\s*$/x){ $croom = ""; $cbldg = ""; $cloc = ""; $cnum = $1 if defined $1; $cname = $2 if defined $2; $ccred = $3 if defined $3; $cprof = $4 if defined $4; $cdow = $5 if defined $5; $start = $6 if defined $6; $end = $7 if defined $7; $cbldg = $8 if defined $8; $croom = $9 if defined $9; $cname =~ s/\s+$//; # munch the end whitespace $cname =~ s/(\w+)\b/ucfirst lc $1/eg; $cprof =~ s/\s+$//; # munch the end whitespace $cprof =~ s/(\w+)\b/ucfirst lc $1/eg; $cname =~ s/\b([ivx]+)\b/uc $1/ie; $cloc = "$cbldg-$croom"; # 4002-515-01 ROUTING & SWITCHING 4 TBA M 1000AM 1120AM 12 3215 }elsif($line=~ /^\s*(?:(....\-...\-..)\s+(.{24})\s+(\d)\s+(.{8}))?\s+(.{5})(......)\s+(......)\s+(\w{1,4})(?:\s+(\w{1,4}))?\s*$/){ $croom = ""; $cbldg = ""; $cloc = ""; $cnum = $1 if defined $1; $cname = $2 if defined $2; $ccred = $3 if defined $3; $cprof = $4 if defined $4; $cdow = $5 if defined $5; $start = $6 if defined $6; $end = $7 if defined $7; $cbldg = $8 if defined $8; $croom = $9 if defined $9; $cname =~ s/\s+$//; # munch the end whitespace $cname =~ s/(\w+)\b/ucfirst lc $1/eg; $cprof =~ s/\s+$//; # munch the end whitespace $cprof =~ s/(\w+)\b/ucfirst lc $1/eg; $cname =~ s/\b([ivx]+)\b/uc $1/ie; $cloc = "$cbldg-$croom"; # 1051 -231 -40 IMAGING 2 BARNEY W 1200N 1250PM 76 1235 }elsif($line=~ /^\s*(?:(....\-...\-..)\s+(.{20})\s+(\d)\s+(.{13}))?\s+(.{5})(.{6})\s+?(.{6})\s+(\w{1,4})(?:\s+(\w{1,4}))?\s*$/){ $croom = ""; $cbldg = ""; $cloc = ""; $cnum = $1 if defined $1; $cname = $2 if defined $2; $ccred = $3 if defined $3; $cprof = $4 if defined $4; $cdow = $5 if defined $5; $start = $6 if defined $6; $end = $7 if defined $7; $cbldg = $8 if defined $8; $croom = $9 if defined $9; $cname =~ s/\s+$//; # munch the end whitespace $cname =~ s/(\w+)\b/ucfirst lc $1/eg; $cprof =~ s/\s+$//; # munch the end whitespace $cprof =~ s/(\w+)\b/ucfirst lc $1/eg; $cname =~ s/\b([ivx]+)\b/uc $1/ie; $cloc = "$cbldg-$croom"; }elsif($line=~ /\s*\_?\s+(....\-...\-..)\s+(.{23})\s*(\d)\s+(.{5})(......)\s+(......)/){ $cnum = $1; $cname = $2; $ccred = $3; $cdow = $4; $start = $5; $end = $6; $cname =~ s/\s+$//; # munch the end whitespace $cname =~ s/(\w+)\b/ucfirst lc $1/eg; $cname =~ s/\b([ivx]+)\b/uc $1/ie; $cloc = "00-0000"; # CALCULUS II RADIN MTRF 1100AM 1150AM 08 2355 }elsif($line=~ /(.{23})\s*(.{12})\s*(.{5})\s*(.{6})\s*(.{6})\s+(\w{1,4})\s+(\w{1,4})/){ $cname = $1; $cprof = $2; $cdow = $3; $start = $4; $end = $5; $cbldg = $6; $croom = $7; $cname =~ s/\s+$//; # munch the end whitespace $cname =~ s/(\w+)\b/ucfirst lc $1/eg; $cprof =~ s/\s+$//; # munch the end whitespace $cprof =~ s/(\w+)\b/ucfirst lc $1/eg; $cname =~ s/\b([ivx]+)\b/uc $1/ie; $cloc = "$cbldg-$croom"; }elsif( $line =~ /\s+(.{5})(.....)M\s+(.....)M/ ){ $cdow = $1; $start = $2; $end = $3; # make it so you can read CSV output back in }elsif( $line =~ /\s*(.+?)\-(.+?)[,\t]\s*(.+?)[,\t]\s*(.+?)[,\t]\s*(.+?)$/ ){ $start = $1; $end = $2; $cname = $3; $cloc = $4; $cdow = $5; # set these to empty as they're not defined on this line $cbldg = ""; $croom = ""; $cnum = ""; $cprof = ""; $ccred = ""; $cbldg = $cloc; if( $cloc =~ /(\d{2})\-(.+)/ ){ $cbldg = $1; $croom = $2; } }else{ warn "cannot parse line: \n$line\n" if $line =~ /\S/; next; } $cdow =~ s/\s//g; $start = convtime($start); $end = convtime($end); if( $usexml ){ my $elt; if( $pcnum eq "" || $cnum ne $pcnum ){ if( @ccont ){ $elt = new XML::Twig::Elt( "course", \%catts, @ccont ); $elt->paste('last_child', $cur_term); @ccont = (); %catts = (); } $catts{'credits'} = $ccred if $ccred; if( $cnum ){ push @ccont, new XML::Twig::Elt('id', $cnum); } push @ccont, new XML::Twig::Elt('title', $cname); if( $cprof ){ $elt = new XML::Twig::Elt('lastname', $cprof); push @ccont, new XML::Twig::Elt('address', $elt); } } my %atts; $atts{'room'} = $croom if ( defined $croom && $croom ne "" && $croom ne "0000" ); $atts{'building'} = $cbldg if ( defined $cbldg && $cbldg ne "" && $cbldg ne "00" ); $atts{'start'} = $start; $atts{'end'} = $end; my $days = join " ", (split //, $cdow ); $atts{'days'} = $days; push @ccont, new XML::Twig::Elt('meeting', \%atts, '#EMPTY') unless ($days eq "T B A" || ($atts{building} eq "NA" && $atts{room} eq "NA" )); # previous course number. if( $pcnum eq "" || $cnum ne $pcnum ){ $pcnum = $cnum; } }else{ ## printout my $tab; if( defined $tab ){ $cname .= "\t" if length $cname < 16; print "$start-$end\t$cname\t$cloc\t$cdow\n"; }else{ print "$start-$end,$cname,$cloc,$cdow\n"; } } } # purge any remaining courses if( @ccont ){ my $elt = new XML::Twig::Elt( "course", \%catts, @ccont ); $elt->paste('first_child', $cur_term); } if( $update_xml ){ my $out; print STDERR "Writing schedule to \"$file\"..."; open $out, ">$file" or die "cannot open $file for writing: $!"; $t->print($out); close $out; print STDERR "done\n"; }else{ $t->print(); print "\n"; } sub convtime{ my ($time) = @_; if($time =~ /(\d+)(\d\d)(\w)/){ $time = (($3 eq 'P' && $1 != 12) ? $1 + 12 : $1).":$2"; } return $time; } sub to_palm{ my ($file) = @_; my $title = $opts{'name'} if $opts{'name'}; print "ScheduleDB:$title\n\n"; my $t = new XML::Twig( TwigHandlers=> {'course' => \&palm_print_course} ); $t->parsefile( $file ); } sub by_date{ # my ( $a, $b ) = @_; my ($a_min, $b_min); if( $a && $a->att('start') =~ /(\d+)\:(\d+)/ ){ $a_min = $1 * 60 + $2; } if( $b && $b->att('start') =~ /(\d+)\:(\d+)/ ){ $b_min = $1 * 60 + $2; } $a_min <=> $b_min; } sub palm_print_course{ my ($t, $elt) = @_; if( exists $opts{'name'} && $elt->parent->att('name') eq $opts{'name'} ){ foreach my $meeting ( $elt->children('meeting') ){ my $squish_day = $meeting->att('days'); $squish_day =~ s/\W//g; my $title = $elt->first_child('title')->text; $title =~ s/,//g; # we can't have a comma in the title my $room = ""; if( $meeting->att('building') && $meeting->att('room') ){ $room = $meeting->att('building')."-".$meeting->att('room'); }elsif( $meeting->att('building') ){ $room = $meeting->att('building'); }elsif( $meeting->att('room') ){ $room = $meeting->att('room'); } my @palm; push @palm, $meeting->att('start')."-".$meeting->att('end'); push @palm, $title; push @palm, $room; push @palm, $squish_day; print join (",", @palm), "\n"; } } } sub usage{ die< this program released under the terms of the GPL USAGE }