package WebService::Nextbus::Agency; use 5.006; use strict; use warnings; use integer; our $VERSION = '0.12'; sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = { _nameCode => undef, _routeRegExp => undef, _dirRegExp => undef, _routes => {}, }; bless ($self, $class); } # Input or check the name code for this agency, e.g. sf-muni sub nameCode { my $self = shift; if (@_) { $self->{_nameCode} = shift } return $self->{_nameCode}; } # Input or check the RegExps used for default parsing sub routeRegExp { my $self = shift; if (@_) { $self->{_routeRegExp} = shift } return $self->{_routeRegExp}; } sub dirRegExp { my $self = shift; if (@_) { $self->{_dirRegExp} = shift } return $self->{_dirRegExp}; } # For building or checking the tree structure of routes, dirs, stops sub routes { my $self = shift; if (@_) { %{$self->{_routes}} = %{$_[0]} } return \%{$self->{_routes}}; } sub dirs { my $self = shift; my ($route, $newDirs) = @_; if ($newDirs) { %{$self->routes()->{$route}} = %$newDirs } return \%{$self->routes()->{$route}}; } sub stops { my $self = shift; my ($route, $dir, $newStops) = @_; if ($newStops) { %{$self->dirs($route)->{$dir}} = %$newStops } return \%{$self->dirs($route)->{$dir}}; } # Input or check a particular stop code given the route, dir, and name of stop. sub stopCode { my $self = shift; my ($route, $dir, $stopName, $newCode) = @_; if ($newCode) { $self->stops($route, $dir)->{$stopName} = $newCode } return $self->stops($route, $dir)->{$stopName}; } # Spit out the stop names (keys) or codes (values) sub allStopNames { my $self = shift; my ($route, $dir) = @_; return keys(%{$self->stops($route, $dir)}); } sub allStopCodes { my $self = shift; my ($route, $dir) = @_; return values(%{$self->stops($route, $dir)}); } # Default parsing of input string according to object's stored RegExps sub parseRoute { my $self = shift; my ($str) = @_; my $routeRegExp = $self->routeRegExp(); my ($route) = ($str =~ /$routeRegExp/i); $str =~ s/$route\s*//; return (ucfirst($route), $str); } sub parseDir { my $self = shift; my ($str) = @_; my $dirRegExp = $self->dirRegExp(); my ($dir) = ($str =~ /$dirRegExp/i); $str =~ s/$dir\s*//; return ($dir, $str); } # Search for stop codes in current tree. First, check whether the input string # directly matches a stop code. Otherwise, assume the input string is a stop # name and search the names for a match. The matching is done word by word: # first split the input at whitespaces, then match each word in turn, narrowing # the list of stopnames at each step (but if the word makes no matches, then # leave the list alone). At the end, return all remaining matches. sub str2stopCodes { my $self = shift; my ($route, $dir, $stopStr) = @_; my @stopCodes = $self->allStopCodes($route, $dir); if ((my @retCodes = grep(/$stopStr/i, @stopCodes)) == 1) { return @retCodes; } my @stopNames = $self->allStopNames($route, $dir); foreach my $word (split(/\s+/, $stopStr)) { if (my @temp = grep(/$word/i, @stopNames)) { @stopNames = @temp; } } my @retCodes; foreach my $stopName (@stopNames) { my $retCode = $self->stopCode($route, $dir, $stopName); push(@retCodes, $retCode); } return @retCodes; } # To dump the routes tree in human readable format. Essentially the same as # Data::Dumper in case you don't want to load that library. sub routesAsString { my $self = shift; foreach my $routeKey (keys(%{$self->routes()})) { print "$routeKey =>\n"; my $routeVal = $self->routes()->{$routeKey}; foreach my $dirKey (keys(%$routeVal)) { print " $dirKey =>\n"; my $dirVal = $routeVal->{$dirKey}; foreach my $stopKey (keys(%$dirVal)) { print " $stopKey => "; my $stopVal = $dirVal->{$stopKey}; print $stopVal . "\n"; } } } } 1 __END__ =head1 NAME WebService::Nextbus::Agency - Superclass for data structures designed for Nextbus website (www.nextbus.com) =head1 SYNOPSIS use WebService::Nextbus; $nb = new WebService::Nextbus; $nb->buildAgency('sf-muni'); # Scraping the webpages repeatedly can take time @stops = $nb->agencies->{'sf-muni'}->str2stopCodes('N', 'judah', 'Chu Dub'); # OR... use WebService::Nextbus::Agency::SFMUNI; $muniAgency = new WebService::Nextbus::Agency::SFMUNI; @stops = $muniAgency->str2stopCodes('N', 'judah', 'Church and Duboce'); C<@stops> can now be used as valid GET arguments on the nextbus webpage. =head1 DESCRIPTION WebService::Nextbus::Agency implements a basic data structure for storing and retrieving information on the various agencies monitored by the Nextbus website (www.nextbus.com). Nextbus provides a GPS system for predicting the arrival times of transit vehicles. In order to screen scrape the website effectively, one must be familiar with the GET arguments used by the site. This module provides the data structure for storing this info once it has been determined for a particular transit agency. The L indicates how the object can be used to retrieve the GET argument that the website requires for returning GPS information for a particular stop on a particular route of the sf-muni agency. There are basically two methods: determine the relevant GET arguments by screen scraping the Nextbus website using L, or load a prepared data structure from a helper subclass. So far, only the L subclass has been implemented. With the screen scraping of WebService::Nextbus available, however, it should be simple to create more helper subclasses by running WebService::Nextbus->buildAgency for the agency of choice and then using Storable to serialize the resulting routes tree for retrieval by the new helper subclass. Once the proper GET code has been retrieved, a web useragent can use the argument to build a URL for the desired information. This useragent function will probably eventually be incorporated into L, which is already a L. The entire functionality of this module will eventually be made obsolete by the anticipated development of an official WebService (W3C standard) by the Nextbus team. However, this is useful in the interim, and if Nextbus ends up charging for their service, then this will continue to be developed. =head2 EXPORT None by default; OO interface. =head1 ERROR CHECKING Watch out! No error checking yet... =head1 REQUIRES Tests require the Test::More module. Subclass SFMUNI (and likely any additional subclasses created for other agencies) requires the L module. =head1 AUTHOR Peter H. Liphli@cpan.org =head1 COPYRIGHT Licensed by Creative Commons http://creativecommons.org/licenses/by-nc-sa/2.0/ =head1 SEE ALSO L, L, L, L, L. =cut ~