File Coverage

blib/lib/Travel/Status/DE/HAFAS/StopFinder.pm
Criterion Covered Total %
statement 23 91 25.2
branch 0 22 0.0
condition 0 9 0.0
subroutine 8 14 57.1
pod 4 4 100.0
total 35 140 25.0


line stmt bran cond sub pod time code
1             package Travel::Status::DE::HAFAS::StopFinder;
2              
3 5     5   44 use strict;
  5         42  
  5         211  
4 5     5   28 use warnings;
  5         10  
  5         330  
5 5     5   98 use 5.014;
  5         17  
6 5     5   27 use utf8;
  5         9  
  5         41  
7              
8 5     5   211 use Carp qw(confess);
  5         10  
  5         433  
9 5     5   29 use Encode qw(decode);
  5         11  
  5         268  
10 5     5   57 use JSON;
  5         11  
  5         51  
11 5     5   743 use LWP::UserAgent;
  5         14  
  5         5865  
12              
13             our $VERSION = '6.25';
14              
15             # {{{ Constructors
16              
17             sub new {
18 0     0 1   my ( $obj, %conf ) = @_;
19              
20 0   0       my $lang = $conf{language} // 'd';
21 0           my $ua = $conf{ua};
22              
23 0 0 0       if ( not $ua and not $conf{async} ) {
24 0   0       my %lwp_options = %{ $conf{lwp_options} // { timeout => 10 } };
  0            
25 0           $ua = LWP::UserAgent->new(%lwp_options);
26 0           $ua->env_proxy;
27             }
28              
29 0           my $reply;
30              
31 0 0         if ( not $conf{input} ) {
32 0           confess('You need to specify an input value');
33             }
34 0 0         if ( not $conf{url} ) {
35 0           confess('You need to specify a URL');
36             }
37              
38             my $ref = {
39             developer_mode => $conf{developer_mode},
40             post => {
41             getstop => 1,
42             REQ0JourneyStopsS0A => 255,
43             REQ0JourneyStopsS0G => $conf{input},
44             },
45 0           };
46              
47 0           bless( $ref, $obj );
48              
49 0 0         if ( $conf{async} ) {
50 0           return $ref;
51             }
52              
53 0           my $url = $conf{url} . "/${lang}n";
54              
55 0           $reply = $ua->post( $url, $ref->{post} );
56              
57 0 0         if ( $reply->is_error ) {
58 0           $ref->{errstr} = $reply->status_line;
59 0           return $ref;
60             }
61              
62 0           $ref->{raw_reply} = $reply->decoded_content;
63              
64 0           $ref->{raw_reply} =~ s{ ^ SLs [.] sls = }{}x;
65 0           $ref->{raw_reply} =~ s{ ; SLs [.] showSuggestion [(] [)] ; $ }{}x;
66              
67 0 0         if ( $ref->{developer_mode} ) {
68 0           say $ref->{raw_reply};
69             }
70              
71 0           $ref->{json} = from_json( $ref->{raw_reply} );
72              
73 0           return $ref;
74             }
75              
76             sub new_p {
77 0     0 1   my ( $obj, %conf ) = @_;
78 0           my $promise = $conf{promise}->new;
79              
80 0 0         if ( not $conf{input} ) {
81 0           return $promise->reject('You need to specify an input value');
82             }
83 0 0         if ( not $conf{url} ) {
84 0           return $promise->reject('You need to specify a URL');
85             }
86              
87 0           my $self = $obj->new( %conf, async => 1 );
88 0           $self->{promise} = $conf{promise};
89              
90 0   0       my $lang = $conf{language} // 'd';
91 0           my $url = $conf{url} . "/${lang}n";
92             $conf{user_agent}->post_p( $url, form => $self->{post} )->then(
93             sub {
94 0     0     my ($tx) = @_;
95 0 0         if ( my $err = $tx->error ) {
96 0           $promise->reject(
97             "POST $url returned HTTP $err->{code} $err->{message}");
98 0           return;
99             }
100 0           my $content = $tx->res->body;
101              
102 0           $self->{raw_reply} = $content;
103              
104 0           $self->{raw_reply} =~ s{ ^ SLs [.] sls = }{}x;
105 0           $self->{raw_reply} =~ s{ ; SLs [.] showSuggestion [(] [)] ; $ }{}x;
106              
107 0 0         if ( $self->{developer_mode} ) {
108 0           say $self->{raw_reply};
109             }
110              
111 0           $self->{json} = from_json( $self->{raw_reply} );
112              
113 0           $promise->resolve( $self->results );
114 0           return;
115             }
116             )->catch(
117             sub {
118 0     0     my ($err) = @_;
119 0           $promise->reject($err);
120 0           return;
121             }
122 0           )->wait;
123              
124 0           return $promise;
125             }
126              
127             # }}}
128              
129             sub errstr {
130 0     0 1   my ($self) = @_;
131              
132 0           return $self->{errstr};
133             }
134              
135             sub results {
136 0     0 1   my ($self) = @_;
137              
138 0           $self->{results} = [];
139              
140 0           for my $result ( @{ $self->{json}->{suggestions} } ) {
  0            
141 0 0         if ( $result->{typeStr} eq '[Bhf/Hst]' ) {
142             push(
143 0           @{ $self->{results} },
144             {
145             name => decode( 'iso-8859-15', $result->{value} ),
146             id => $result->{extId}
147             }
148 0           );
149             }
150             }
151              
152 0           return @{ $self->{results} };
  0            
153             }
154              
155             1;
156              
157             __END__
158              
159             =head1 NAME
160              
161             Travel::Status::DE::HAFAS::StopFinder - Interface to HAFAS-based online stop
162             finder services
163              
164             =head1 SYNOPSIS
165              
166             use Travel::Status::DE::HAFAS::StopFinder;
167              
168             my $sf = Travel::Status::DE::HAFAS::StopFinder->new(
169             url => 'https://reiseauskunft.bahn.de/bin/ajax-getstop.exe',
170             input => 'Borbeck',
171             );
172              
173             if (my $err = $sf->errstr) {
174             die("Request error: ${err}\n");
175             }
176              
177             for my $candidate ($sf->results) {
178             printf("%s (%s)\n", $candidate->{name}, $candidate->{id});
179             }
180              
181             =head1 VERSION
182              
183             version 6.25
184              
185             =head1 DESCRIPTION
186              
187             Travel::Status::DE::HAFAS::StopFinder is an interface to the stop finder
188             service of HAFAS based arrival/departure monitors, for instance the one
189             available at L<https://reiseauskunft.bahn.de/bin/ajax-getstop.exe/dn>.
190              
191             It takes a string (usually a location or station name) and reports all
192             stations and stops which are lexically similar to it.
193              
194             StopFinder typically gives less coarse results than
195             Travel::Status::DE::HAFAS(3pm)'s locationSearch method. However, it is unclear
196             whether HAFAS instances will continue supporting it in the future.
197              
198             =head1 METHODS
199              
200             =over
201              
202             =item my $stopfinder = Travel::Status::DE::HAFAS::StopFinder->new(I<%opts>)
203              
204             Looks up stops as specified by I<opts> and teruns a new
205             Travel::Status::DE::HAFAS::StopFinder element with the results. Dies if the
206             wrong I<opts> were passed.
207              
208             Supported I<opts> are:
209              
210             =over
211              
212             =item B<input> => I<string>
213              
214             string to look up, e.g. "Borbeck" or "Koeln Bonn Flughafen". Mandatory.
215              
216             =item B<url> => I<url>
217              
218             Base I<url> of the stop finder service, without the language and mode
219             suffix ("/dn" and similar). Mandatory. See Travel::Status::DE::HAFAS(3pm)'s
220             B<get_services> method for a list of URLs.
221              
222             =item B<language> => I<language>
223              
224             Set language. Accepted arguments are B<d>eutsch, B<e>nglish, B<i>talian and
225             B<n> (dutch), depending on the used service.
226              
227             It is unknown if this option has any effect.
228              
229             =item B<lwp_options> => I<\%hashref>
230              
231             Passed on to C<< LWP::UserAgent->new >>. Defaults to C<< { timeout => 10 } >>,
232             you can use an empty hashref to override it.
233              
234             =back
235              
236             =item my $stopfinder_p = Travel::Status::DE::HAFAS::StopFinder->new_p(I<%opt>)
237              
238             Return a promise that resolves into a list of
239             Travel::Status::DE::HAFAS::StopFinder results ($stopfinder->results) on success
240             and rejects with an error message ($stopfinder->errstr) on failure. In addition
241             to the arguments of B<new>, the following mandatory arguments must be set.
242              
243             =over
244              
245             =item B<promise> => I<promises module>
246              
247             Promises implementation to use for internal promises as well as B<new_p> return
248             value. Recommended: Mojo::Promise(3pm).
249              
250             =item B<user_agent> => I<user agent>
251              
252             User agent instance to use for asynchronous requests. The object must implement
253             a B<post_p> function. Recommended: Mojo::UserAgent(3pm).
254              
255             =back
256              
257             =item $stopfinder->errstr
258              
259             In case of an error in the HTTP request, returns a string describing it. If
260             no error occurred, returns undef.
261              
262             =item $stopfinder->results
263              
264             Returns a list of stop candidates. Each list element is a hash reference. The
265             hash keys are B<id> (IBNR / EVA / UIC station code) and B<name> (stop name).
266             Both can be used as input for the Travel::Status::DE::HAFAS(3pm) constructor.
267              
268             If no matching results were found or the parser / HTTP request failed, returns
269             the empty list.
270              
271             =back
272              
273             =head1 DIAGNOSTICS
274              
275             None.
276              
277             =head1 DEPENDENCIES
278              
279             =over
280              
281             =item * LWP::UserAgent(3pm)
282              
283             =item * JSON(3pm)
284              
285             =back
286              
287             =head1 BUGS AND LIMITATIONS
288              
289             Unknown.
290              
291             =head1 SEE ALSO
292              
293             Travel::Status::DE::HAFAS(3pm).
294              
295             =head1 AUTHOR
296              
297             Copyright (C) 2015-2023 by Birte Kristina Friesel E<lt>derf@finalrewind.orgE<gt>
298              
299             =head1 LICENSE
300              
301             This module is licensed under the same terms as Perl itself.