File Coverage

blib/lib/WebService/OCTranspo.pm
Criterion Covered Total %
statement 39 137 28.4
branch 4 68 5.8
condition n/a
subroutine 11 18 61.1
pod 2 3 66.6
total 56 226 24.7


line stmt bran cond sub pod time code
1             package WebService::OCTranspo;
2 2     2   691073 use strict;
  2         5  
  2         89  
3 2     2   15 use warnings;
  2         6  
  2         71  
4              
5 2     2   2764 use WWW::Mechanize;
  2         717460  
  2         99  
6 2     2   2305 use HTML::Form::ForceValue;
  2         27109  
  2         20  
7 2     2   4791 use HTML::TableExtract;
  2         22547  
  2         18  
8 2     2   182 use HTTP::Status;
  2         6  
  2         996  
9              
10 2     2   16 use Carp;
  2         4  
  2         4459  
11              
12             our $VERSION = '0.027';
13              
14             my $DEBUG = 0;
15 3     3 0 13 sub DEBUG { $DEBUG };
16              
17             sub new
18             {
19 1     1 1 1298 my ($class, $args) = @_;
20              
21 1 50       6 if( $args->{debug} ) {
22 0         0 $DEBUG = $args->{debug};
23             }
24              
25 1         4 my $self = {
26             stop_data => {},
27             };
28 1         15 $self->{mech} = WWW::Mechanize->new(
29             cookie_jar => {},
30             agent => 'WebService-OCTranspo/' . $VERSION,
31             quiet => 1,
32             );
33              
34 1         35224 bless $self, $class;
35 1         8 return $self;
36             }
37              
38             # TODO: schedule_for_stop should return an object, not a hashref.
39             sub schedule_for_stop
40             {
41 5     5 1 6917625 my( $self, $args ) = @_;
42              
43 5         14 foreach my $key ( qw( stop_id route_id date) ) {
44 13 100       48 if( ! exists $args->{$key} ) {
45 2         277 croak qq{$key argument required for schedule_for_stop()};
46             }
47             }
48              
49             # Force date into Eastern time, if it isn't already
50 3         27 $args->{date}->set_time_zone('America/Toronto');
51              
52 3         5053 $self->_reset();
53 0         0 $self->_select_date( $args->{date} );
54 0         0 $self->{stop_data}{date} = $args->{date};
55              
56 0 0       0 if( ! $self->_select_stop( $args->{stop_id} ) ) {
57 0         0 die "Stop $args->{stop_id} does not seem to exist";
58             }
59              
60 0         0 $self->{stop_data}{stop_number} = $args->{stop_id};
61              
62 0 0       0 if( ! $self->_select_route( $args->{route_id} ) ) {
63 0         0 die "Route $args->{route_id} does not service that stop";
64             }
65              
66 0         0 $self->{stop_data}{route_number} = $args->{route_id};
67              
68 0         0 return $self->_parse_schedule();
69             }
70              
71             sub _reset
72             {
73 3     3   7 my ($self) = @_;
74              
75 3         15 $self->{stop_data} = {};
76             # Get the form page
77 3 50       12 warn 'Fetching start page for new session' if DEBUG;
78              
79             # More evil. Their broken HTML has an
80             # which is completely invalid. So... catch the warning from
81             # HTML::Form and ignore it.
82             local $SIG{__WARN__} = sub {
83 0 0   0   0 warn $_[0] unless $_[0] =~ m/^Unknown input type 'input' at/;
84 3         22 };
85              
86 3         20 $self->{mech}->get('http://www.octranspo.com/tps/jnot/sptStartEN.oci');
87             }
88              
89             sub _select_date
90             {
91 0     0     my ($self, $date) = @_;
92             # Select the form
93 0 0         warn 'Selecting form via mech' if DEBUG;
94 0           $self->{mech}->form_name('spt_date');
95 0 0         warn $self->{mech}->current_form->dump if DEBUG;
96              
97 0           my $form = $self->{mech}->current_form();
98              
99             # Disable 'readonly' attribute
100 0           $form->find_input( 'travelDate' )->readonly(0);
101 0           $form->find_input( 'visibleDate' )->readonly(0);
102              
103 0 0         warn 'Forcing form values' if DEBUG;
104             # Force some values. Yes, all this duplication is necessary.
105 0           $form->force_value('theDate', $date->ymd);
106 0           $form->force_value('travelDate', $date->ymd);
107 0           $form->force_value('visibleDate', $date->month_name . ' ' . $date->day);
108 0           $form->force_value('theTime', '0000');
109              
110 0 0         warn 'Submitting date form' if DEBUG;
111 0           $self->{mech}->click();
112              
113 0           return 1;
114             }
115              
116             sub _select_stop
117             {
118 0     0     my ($self, $stop_id) = @_;
119             # Select a stop number
120 0 0         warn 'Selecting stop form' if DEBUG;
121 0           $self->{mech}->form_name('spt_choose560');
122 0 0         warn $self->{mech}->current_form->dump if DEBUG;
123 0           $self->{mech}->current_form->force_value('the560number', $stop_id);
124 0 0         warn 'Submitting stop form' if DEBUG;
125 0           $self->{mech}->click();
126              
127             # Confirm the stop
128 0 0         warn 'Selecting stop confirm form' if DEBUG;
129 0 0         if( ! defined $self->{mech}->form_name('spt_confirm560') ) {
130 0           return 0;
131             }
132 0 0         warn $self->{mech}->current_form->dump if DEBUG > 1;
133              
134 0           $self->{stop_data}{stop_name} = $self->_extract_stop_name(
135             $stop_id,
136             $self->{mech}->content
137             );
138              
139 0 0         warn 'Submitting stop confirm form' if DEBUG;
140 0           $self->{mech}->click();
141              
142 0           return 1;
143             }
144              
145             sub _extract_stop_name
146             {
147 0     0     my ($self, $stop_id, $content) = @_;
148 0 0         warn "Looking for stop name in page content" if DEBUG;
149              
150 0           my ($name) = $content =~ m{
151             Is\sthis\sthe\sright\sbus\sstop\?
152             \s+
153             \($stop_id\)
154             \s+
155             ([^<]+)<
156             }sx;
157              
158 0 0         if( $name ) {
159 0           $name =~ s/\s+$//;
160 0 0         warn "Found name $name" if DEBUG;
161              
162 0           return $name;
163             }
164              
165 0           return 'unknown';
166             }
167              
168             sub _select_route
169             {
170 0     0     my ($self, $route_id) = @_;
171             # By now we may have data for one-route stops, but not for
172             # multi-route stops.
173             # Need to parse the output and:
174             # a) if it's asking for a route number, find the one we want and select
175             # the appropriate checkbox
176             # b) if it's not, parse the output for the stop data
177 0 0         if( ! defined $self->{mech}->form_name('spt_selectRoutes') ) {
178             # No route form, so it's a single-route stop
179 0           return 1;
180             }
181 0 0         warn "Looking for $route_id" if DEBUG;
182              
183 0           my ($checkname) = $self->{mech}->content =~ m{
184              
185 0 0         if( !$checkname ) {
186 0           return 0;
187             }
188              
189 0 0         warn "Got checkbox name $checkname" if DEBUG;
190              
191 0           $self->{mech}->form_name('spt_selectRoutes');
192 0 0         warn $self->{mech}->current_form->dump if DEBUG;
193 0           $self->{mech}->current_form()->force_value($checkname, 1);
194 0           $self->{mech}->click();
195              
196 0           return 1;
197             }
198              
199             sub _parse_schedule
200             {
201 0     0     my ($self) = @_;
202              
203 0           $self->{stop_data}{route_name} = $self->_extract_route_name(
204             $self->{stop_data}{route_number},
205             $self->{mech}->content,
206             );
207              
208 0           my %schedule = %{ $self->{stop_data} };
  0            
209 0           $schedule{times} = [];
210 0           $schedule{notes} = {};
211              
212 0 0         warn $self->{mech}->content if DEBUG > 2;
213              
214 0           my $te = HTML::TableExtract->new( attribs => { class => 'spt_table' } );
215 0           $te->parse( $self->{mech}->content );
216              
217 0           foreach my $ts ( $te->tables ) {
218 0 0         warn 'Table (', join(q{,}, $ts->coords), '):' if DEBUG;
219 0           foreach my $row ( $ts->rows ) {
220 0           foreach my $cell ( @$row ) {
221 0 0         next if ! defined $cell;
222 0           $cell =~ s/^\s+//s;
223 0           $cell =~ s/\s+$//s;
224 0           $cell =~ s/\s+/ /gs;
225 0 0         if( $cell =~ m/^\d+:\d+/ ) {
226 0           push @{$schedule{'times'}}, $cell;
  0            
227             }
228             }
229             }
230             }
231              
232 0 0         warn "Now looking for stop note info" if DEBUG;
233              
234 0           $te = HTML::TableExtract->new( headers => [ 'Stop Note Information' ] );
235 0           $te->parse( $self->{mech}->content ) ;
236              
237 0 0         if( $te->tables ) {
238 0           foreach my $row ($te->rows) {
239 0           my ($key, $value) = split(/\s*-\s*/, $row->[0], 2);
240 0           $schedule{notes}{$key} = $value;
241             }
242             }
243              
244 0           return \%schedule;
245             }
246              
247             sub _extract_route_name
248             {
249 0     0     my ($self, $route_id, $content) = @_;
250 0 0         warn "Looking for route name in page content" if DEBUG;
251              
252 0           my ($name) = $content =~ m{
253             $route_id
254             \s
255             -
256             \s
257             ([^<]+)
258             }sx;
259              
260 0 0         if( $name ) {
261 0           $name =~ s/\s+$//;
262 0 0         warn "Found name $name" if DEBUG;
263              
264 0           return $name;
265             }
266              
267 0           return 'unknown';
268             }
269              
270             1;
271             __END__