From 13b9dcdf70eb95c74e8971122bbe5b268b81bd50 Mon Sep 17 00:00:00 2001 From: Daniel Friesel Date: Thu, 23 Jun 2011 16:47:12 +0200 Subject: Rename to Travel::Routing::DE::VRR --- Build.PL | 2 +- lib/Net/Travel/DE/VRR.pm | 712 -------------------------------- lib/Net/Travel/DE/VRR/Route.pm | 99 ----- lib/Net/Travel/DE/VRR/Route/Part.pm | 150 ------- lib/Travel/Routing/DE/VRR.pm | 712 ++++++++++++++++++++++++++++++++ lib/Travel/Routing/DE/VRR/Route.pm | 99 +++++ lib/Travel/Routing/DE/VRR/Route/Part.pm | 150 +++++++ t/20-vrr.t | 6 +- 8 files changed, 965 insertions(+), 965 deletions(-) delete mode 100644 lib/Net/Travel/DE/VRR.pm delete mode 100644 lib/Net/Travel/DE/VRR/Route.pm delete mode 100644 lib/Net/Travel/DE/VRR/Route/Part.pm create mode 100644 lib/Travel/Routing/DE/VRR.pm create mode 100644 lib/Travel/Routing/DE/VRR/Route.pm create mode 100644 lib/Travel/Routing/DE/VRR/Route/Part.pm diff --git a/Build.PL b/Build.PL index dfb4a93..827db39 100644 --- a/Build.PL +++ b/Build.PL @@ -11,7 +11,7 @@ my %opts = ( 'Test::Pod' => 0, 'Test::Command' => 0, }, - module_name => 'Net::Travel::DE::VRR', + module_name => 'Travel::Routing::DE::VRR', license => 'unrestricted', requires => { 'Class::Accessor' => 0, diff --git a/lib/Net/Travel/DE/VRR.pm b/lib/Net/Travel/DE/VRR.pm deleted file mode 100644 index 9a20760..0000000 --- a/lib/Net/Travel/DE/VRR.pm +++ /dev/null @@ -1,712 +0,0 @@ -package Net::Travel::DE::VRR; - -use strict; -use warnings; -use 5.010; - -use Carp qw(confess); -use Net::Travel::DE::VRR::Route; -use LWP::UserAgent; -use XML::LibXML; - -our $VERSION = '1.3'; - -sub set_time { - my ( $self, %conf ) = @_; - - my $time; - - if ( $conf{departure_time} ) { - $self->{post}->{itdTripDateTimeDepArr} = 'dep'; - $time = $conf{departure_time}; - } - elsif ( $conf{arrival_time} ) { - $self->{post}->{itdTripDateTimeDepArr} = 'arr'; - $time = $conf{arrival_time}; - } - else { - confess('time: Specify either departure_time or arrival_time'); - } - - if ( $time !~ / ^ [0-2]? \d : [0-5]? \d $ /x ) { - confess("time: must match HH:MM - '${time}'"); - } - - @{ $self->{post} }{ 'itdTimeHour', 'itdTimeMinute' } = split( /:/, $time ); - - return; -} - -sub departure_time { - my ( $self, $time ) = @_; - - return $self->set_time( departure_time => $time ); -} - -sub arrival_time { - my ( $self, $time ) = @_; - - return $self->set_time( arrival_time => $time ); -} - -sub date { - my ( $self, $date ) = @_; - - my ( $day, $month, $year ) = split( /[.]/, $date ); - - if ( not defined $day or not length($day) or $day < 1 or $day > 31 ) { - confess("date: invalid day, must match DD.MM[.[YYYY]] - '${date}'"); - } - if ( not defined $month or not length($month) or $month < 1 or $month > 12 ) - { - confess("date: invalid month, must match DD.MM[.[YYYY]] - '${date}'"); - } - - if ( not defined $year or not length($year) ) { - $year = ( localtime(time) )[5] + 1900; - } - - @{ $self->{post} }{ 'itdDateDay', 'itdDateMonth', 'itdDateYear' } - = ( $day, $month, $year ); - - return; -} - -sub exclude { - my ( $self, @exclude ) = @_; - - my @mapping = qw{ - zug s-bahn u-bahn stadtbahn tram stadtbus regionalbus - schnellbus seilbahn schiff ast sonstige - }; - - foreach my $exclude_type (@exclude) { - my $ok = 0; - for my $map_id ( 0 .. $#mapping ) { - if ( $exclude_type eq $mapping[$map_id] ) { - $self->{post}->{"inclMOT_${map_id}"} = undef; - $ok = 1; - } - } - if ( not $ok ) { - confess("exclude: Unsupported type '${exclude_type}'"); - } - } - - return; -} - -sub max_interchanges { - my ( $self, $max ) = @_; - - $self->{post}->{maxChanges} = $max; - - return; -} - -sub select_interchange_by { - my ( $self, $prefer ) = @_; - - given ($prefer) { - when ('speed') { $self->{post}->{routeType} = 'LEASTTIME' } - when ('waittime') { $self->{post}->{routeType} = 'LEASTINTERCHANGE' } - when ('distance') { $self->{post}->{routeType} = 'LEASTWALKING' } - default { - confess( -"select_interchange_by: Must be speed/waittime/distance: '${prefer}'" - ); - } - } - - return; -} - -sub train_type { - my ( $self, $include ) = @_; - - given ($include) { - when ('local') { $self->{post}->{lineRestriction} = 403 } - when ('ic') { $self->{post}->{lineRestriction} = 401 } - when ('ice') { $self->{post}->{lineRestriction} = 400 } - default { - confess("train_type: Must be local/ic/ice: '${include}'"); - } - } - - return; -} - -sub use_near_stops { - my ( $self, $toggle ) = @_; - - $self->{post}->{useProxFootSearch} = $toggle; - - return; -} - -sub walk_speed { - my ( $self, $walk_speed ) = @_; - - if ( $walk_speed ~~ [ 'normal', 'fast', 'slow' ] ) { - $self->{post}->{changeSpeed} = $walk_speed; - } - else { - confess("walk_speed: Must be normal/fast/slow: '${walk_speed}'"); - } - - return; -} - -sub with_bike { - my ( $self, $bike ) = @_; - - $self->{post}->{bikeTakeAlong} = $bike; - - return; -} - -sub place { - my ( $self, $which, $place, $stop, $type ) = @_; - - if ( not( $place and $stop ) ) { - confess('place: Need >= three elements'); - } - - $type //= 'stop'; - - @{ $self->{post} }{ "place_${which}", "name_${which}" } = ( $place, $stop ); - - if ( $type ~~ [qw[address poi stop]] ) { - $self->{post}->{"type_${which}"} = $type; - } - - return; -} - -sub create_post { - my ($self) = @_; - - my $conf = $self->{config}; - my @now = localtime( time() ); - - $self->{post} = { - changeSpeed => 'normal', - command => q{}, - execInst => q{}, - imparedOptionsActive => 1, - inclMOT_0 => 'on', - inclMOT_1 => 'on', - inclMOT_10 => 'on', - inclMOT_11 => 'on', - inclMOT_2 => 'on', - inclMOT_3 => 'on', - inclMOT_4 => 'on', - inclMOT_5 => 'on', - inclMOT_6 => 'on', - inclMOT_7 => 'on', - inclMOT_8 => 'on', - inclMOT_9 => 'on', - includedMeans => 'checkbox', - itOptionsActive => 1, - itdDateDay => $now[3], - itdDateMonth => $now[4] + 1, - itdDateYear => $now[5] + 1900, - itdLPxx_ShowFare => q{ }, - itdLPxx_command => q{}, - itdLPxx_enableMobilityRestrictionOptionsWithButton => q{}, - itdLPxx_id_destination => ':destination', - itdLPxx_id_origin => ':origin', - itdLPxx_id_via => ':via', - itdLPxx_mapState_destination => q{}, - itdLPxx_mapState_origin => q{}, - itdLPxx_mapState_via => q{}, - itdLPxx_mdvMap2_destination => q{}, - itdLPxx_mdvMap2_origin => q{}, - itdLPxx_mdvMap2_via => q{}, - itdLPxx_mdvMap_destination => q{::}, - itdLPxx_mdvMap_origin => q{::}, - itdLPxx_mdvMap_via => q{::}, - itdLPxx_priceCalculator => q{}, - itdLPxx_transpCompany => 'vrr', - itdLPxx_view => q{}, - itdTimeHour => $now[2], - itdTimeMinute => $now[1], - itdTripDateTimeDepArr => 'dep', - language => 'de', - lineRestriction => 403, - maxChanges => 9, - nameInfo_destination => 'invalid', - nameInfo_origin => 'invalid', - nameInfo_via => 'invalid', - nameState_destination => 'empty', - nameState_origin => 'empty', - nameState_via => 'empty', - name_destination => q{}, - name_origin => q{}, - name_via => q{}, - placeInfo_destination => 'invalid', - placeInfo_origin => 'invalid', - placeInfo_via => 'invalid', - placeState_destination => 'empty', - placeState_origin => 'empty', - placeState_via => 'empty', - place_destination => q{}, - place_origin => q{}, - place_via => q{}, - ptOptionsActive => 1, - requestID => 0, - routeType => 'LEASTTIME', - sessionID => 0, - text => 1993, - trITArrMOT => 100, - trITArrMOTvalue100 => 8, - trITArrMOTvalue101 => 10, - trITArrMOTvalue104 => 10, - trITArrMOTvalue105 => 10, - trITDepMOT => 100, - trITDepMOTvalue100 => 8, - trITDepMOTvalue101 => 10, - trITDepMOTvalue104 => 10, - trITDepMOTvalue105 => 10, - typeInfo_destination => 'invalid', - typeInfo_origin => 'invalid', - typeInfo_via => 'invalid', - type_destination => 'stop', - type_origin => 'stop', - type_via => 'stop', - useRealtime => 1 - }; - - $self->place( 'origin', @{ $conf->{origin} } ); - $self->place( 'destination', @{ $conf->{destination} } ); - - if ( $conf->{via} ) { - $self->place( 'via', @{ $conf->{via} } ); - } - if ( $conf->{arrival_time} || $conf->{departure_time} ) { - $self->set_time( %{$conf} ); - } - if ( $conf->{date} ) { - $self->date( $conf->{date} ); - } - if ( $conf->{exclude} ) { - $self->exclude( @{ $conf->{exclude} } ); - } - if ( $conf->{max_interchanges} ) { - $self->max_interchanges( $conf->{max_interchanges} ); - } - if ( $conf->{select_interchange_by} ) { - $self->select_interchange_by( $conf->{select_interchange_by} ); - } - if ( $conf->{use_near_stops} ) { - $self->use_near_stops(1); - } - if ( $conf->{train_type} ) { - $self->train_type( $conf->{train_type} ); - } - if ( $conf->{walk_speed} ) { - $self->walk_speed( $conf->{walk_speed} ); - } - if ( $conf->{with_bike} ) { - $self->with_bike(1); - } - - return; -} - -sub parse_initial { - my ($self) = @_; - - my $tree = $self->{tree} - = XML::LibXML->load_html( string => $self->{html_reply}, ); - - my $con_part = 0; - my $con_no; - my $cons = []; - - my $xp_td = XML::LibXML::XPathExpression->new('//table//table/tr/td'); - my $xp_img = XML::LibXML::XPathExpression->new('./img'); - - foreach my $td ( @{ $tree->findnodes($xp_td) } ) { - - my $colspan = $td->getAttribute('colspan') // 0; - my $class = $td->getAttribute('class') // q{}; - - if ( $colspan != 8 and $class !~ /^bgColor2?$/ ) { - next; - } - - if ( $colspan == 8 ) { - if ( $td->textContent() =~ m{ (? \d+ ) [.] .+ Fahrt }x ) { - $con_no = $+{no} - 1; - $con_part = 0; - next; - } - } - - if ( $class =~ /^bgColor2?$/ ) { - if ( $class eq 'bgColor' and ( $con_part % 2 ) == 1 ) { - $con_part++; - } - elsif ( $class eq 'bgColor2' and ( $con_part % 2 ) == 0 ) { - $con_part++; - } - } - - if ( defined $con_no - and not $td->exists($xp_img) - and $td->textContent() !~ /^\s*$/ ) - { - push( @{ $cons->[$con_no]->[$con_part] }, $td->textContent() ); - } - } - - return $cons; -} - -sub parse_pretty { - my ( $self, $con_parts ) = @_; - - my @elements; - my @next_extra; - - for my $con ( @{$con_parts} ) { - - my $hash; - - # Note: Changes @{$con} elements - foreach my $str ( @{$con} ) { - $str =~ s/[\s\n\t]+/ /gs; - $str =~ s/^ //; - $str =~ s/ $//; - } - - if ( @{$con} < 5 ) { - @next_extra = @{$con}; - next; - } - - # @extra may contain undef values - foreach my $extra (@next_extra) { - if ($extra) { - push( @{ $hash->{extra} }, $extra ); - } - } - @next_extra = undef; - - if ( $con->[0] !~ / \d{2} : \d{2} /ox ) { - splice( @{$con}, 0, 0, q{} ); - splice( @{$con}, 4, 0, q{} ); - $con->[7] = q{}; - } - elsif ( $con->[4] =~ / Plan: \s ab /ox ) { - push( @{ $hash->{extra} }, splice( @{$con}, 4, 1 ) ); - } - - foreach my $extra ( splice( @{$con}, 8, -1 ) ) { - push( @{ $hash->{extra} }, $extra ); - } - - $hash->{departure_time} = $con->[0]; - - # always "ab" $con->[1]; - $hash->{departure_stop} = $con->[2]; - $hash->{train_line} = $con->[3]; - $hash->{arrival_time} = $con->[4]; - - # always "an" $con->[5]; - $hash->{arrival_stop} = $con->[6]; - $hash->{train_destination} = $con->[7]; - - push( @elements, $hash ); - } - - return Net::Travel::DE::VRR::Route->new(@elements); -} - -sub new { - my ( $obj, %conf ) = @_; - - my $ref = {}; - - $ref->{config} = \%conf; - - bless( $ref, $obj ); - - $ref->create_post(); - - return $ref; -} - -sub submit { - my ( $self, %conf ) = @_; - - $conf{autocheck} = 1; - - $self->{ua} = LWP::UserAgent->new(%conf); - - my $response = $self->{ua} - ->post( 'http://efa.vrr.de/vrr/XSLT_TRIP_REQUEST2', $self->{post} ); - - # XXX (workaround) - # The content actually is iso-8859-1. But HTML::Message doesn't actually - # decode character strings when they have that encoding. However, it - # doesn't check for latin-1, which is an alias for iso-8859-1. - - $self->{html_reply} = $response->decoded_content( charset => 'latin-1' ); - - $self->parse(); - - return; -} - -sub parse { - my ($self) = @_; - - my $raw_cons = $self->parse_initial(); - - for my $raw_con ( @{$raw_cons} ) { - push( @{ $self->{routes} }, $self->parse_pretty($raw_con) ); - } - - $self->check_ambiguous(); - $self->check_no_connections(); - - if ( @{$raw_cons} == 0 ) { - confess('Got no data to parse'); - } - - return 1; -} - -sub check_ambiguous { - my ($self) = @_; - my $tree = $self->{tree}; - - my $xp_select = XML::LibXML::XPathExpression->new('//select'); - my $xp_option = XML::LibXML::XPathExpression->new('./option'); - - foreach my $select ( @{ $tree->findnodes($xp_select) } ) { - - my $post_key = $select->getAttribute('name'); - my @possible; - - foreach my $val ( $select->findnodes($xp_option) ) { - push( @possible, $val->textContent() ); - } - my $err_text = join( q{, }, @possible ); - - confess("Ambiguous input for '${post_key}': '${err_text}'"); - } - - return; -} - -sub check_no_connections { - my ($self) = @_; - my $tree = $self->{tree}; - - my $xp_err_img = XML::LibXML::XPathExpression->new( - '//td/img[@src="images/ausrufezeichen.jpg"]'); - - my $err_node = $tree->findnodes($xp_err_img)->[0]; - - if ($err_node) { - my $text = $err_node->parentNode()->parentNode()->textContent(); - confess("Got no connections: '${text}'"); - } - - return; -} - -sub routes { - my ($self) = @_; - - return @{ $self->{routes} }; -} - -1; - -__END__ - -=head1 NAME - -Net::Travel::DE::VRR - inofficial interface to the efa.vrr.de German itinerary service - -=head1 SYNOPSIS - - use Net::Travel::DE::VRR; - - my $efa = Net::Travel::DE::VRR->new( - origin => [ 'Essen', 'HBf' ], - destination => [ 'Duisburg', 'HBf' ], - ); - - $efa->submit(); - - for my $route ( $efa->routes() ) { - for my $part ( $route->parts() ) { - printf( - "%s at %s -> %s at %s, via %s to %s", - $part->departure_time, $part->departure_stop, - $part->arrival_time, $part->arrival_stop, - $part->train_line, $part->train_destination, - ); - } - print "\n\n"; - } - -=head1 VERSION - -version 1.3 - -=head1 DESCRIPTION - -B is a client for the efa.vrr.de web interface. -You pass it the start/stop of your journey, maybe a time and a date and more -details, and it returns the up-to-date scheduled connections between those two -stops. - -It uses B and B for this. - -=head1 METHODS - -=over - -=item $efa = Net::Travel::DE::VRR->new(I<%conf>) - -Returns a new Net::Travel::DE::VRR object and sets up its POST data via %conf. - -Valid hash keys and their values are: - -=over - -=item B => B<[> IB<,> I [ B<,> I ] B<]> - -Mandatory. Sets the start of the journey. -I is optional and may be one of B (default), B
(street -and house number) or B ("point of interest"). - -=item B => B<[> IB<,> I [ B<,> I ] B<]> - -Mandatory. Sets the end of the journey, see B. - -=item B => B<[> IB<,> I [ B<,> I ] B<]> - -Optional. Specifies an intermediate stop which the resulting itinerary must -contain. See B for arguments. - -=item B => I - -Journey end time - -=item B => I - -Journey start time. Default: now - -=item B => I[I] - -Journey date. Default: tdoay - -=item B => \@exclude - -Do not use certain transport types for itinerary. Accepted arguments: -zug, s-bahn, u-bahn, stadtbahn, tram, stadtbus, regionalbus, schnellbus, -seilbahn, schiff, ast, sonstige - -=item B => I - -Set maximum number of interchanges - -=item B => B|B|B - -Prefer either fast connections (default), connections with low wait time or -connections with little distance to walk - -=item B => B<0>|B<1> - -If true: Try using near stops instead of the specified origin/destination ones - -=item B => B|B|B - -Include only local trains into itinarery (default), all but ICEs, or all. - -The latter two are usually way more expensive for short routes. - -=item B => B|B|B - -Set walk speed. Default: B - -=item B => B<0>|B<1> - -If true: Prefer connections allowing passengers with bikes - -=back - -=item $efa->submit(I<%opts>) - -Submit the query to B. -I<%opts> is passed on to C<< LWP::UserAgent->new(%opts) >>. - -=item $efa->routes() - -Returns a list of Net::Travel::DE::VRR::Route(3pm) elements. Each one contains -one method of getting from start to stop. - -=back - -=head2 ACCESSORS - -The following methods act like the arguments to B. See there. - -=over - -=item $efa->departure_time(I<$time>) - -=item $efa->arrival_time(I<$time>) - -=item $efa->date(I<$date>) - -=item $efa->exclude(I<@exclude>) - -=item $efa->max_interchanges(I<$num>) - -=item $efa->select_interchange_by(I<$selection>) - -=item $efa->train_type(I<$type>) - -=item $efa->use_near_stops(I<$bool>) - -=item $efa->walk_speed(I<$speed>) - -=item $efa->with_bike(I<$bool>) - -=back - -=head1 DIAGNOSTICS - -Dies with a backtrace when anything goes wrong. - -=head1 DEPENDENCIES - -=over - -=item * LWP::UserAgent(3pm) - -=item * XML::LibXML(3pm) - -=back - -=head1 BUGS AND LIMITATIONS - -The parser is still somewhat fragile and has no proper error handling. - -It is best not to pass Unicode characters to B. - -=head1 AUTHOR - -Copyright (C) 2009-2011 by Daniel Friesel Ederf@finalrewind.orgE - -=head1 LICENSE - - 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/lib/Net/Travel/DE/VRR/Route.pm b/lib/Net/Travel/DE/VRR/Route.pm deleted file mode 100644 index 7089120..0000000 --- a/lib/Net/Travel/DE/VRR/Route.pm +++ /dev/null @@ -1,99 +0,0 @@ -package Net::Travel::DE::VRR::Route; - -use strict; -use warnings; -use 5.010; - -use Net::Travel::DE::VRR::Route::Part; - -our $VERSION = '1.3'; - -sub new { - my ( $obj, @parts ) = @_; - - my $ref = {}; - - for my $part (@parts) { - push( - @{ $ref->{parts} }, - Net::Travel::DE::VRR::Route::Part->new( %{$part} ) - ); - } - - return bless( $ref, $obj ); -} - -sub parts { - my ($self) = @_; - - return @{ $self->{parts} }; -} - -1; - -__END__ - -=head1 NAME - -Net::Travel::DE::VRR::Route - Single route (connection) between two points - -=head1 SYNOPSIS - - for my $route ( $efa->routes() ) { - for my $part ( $route->parts() ) { - # $part is a Net::Travel::DE::VRR::Route::Part object - } - } - -=head1 VERSION - -version 1.3 - -=head1 DESCRIPTION - -Net::Travel::DE::VRR::Route describes a single method of getting from one -point to another. It holds a bunch of Net::Travel::DE::VRR::Route::Part(3pm) -objects describing the parts of the route in detail. Each part depends on the -previous one. - -You usually want to acces it via C<< $efa->routes() >>. - -=head1 METHODS - -=over - -=item my $route = Net::Travel::DE::VRR::Route->new(I<@parts>) - -Creates a new Net::Travel::DE::VRR::Route elements consisting of I, -which are Net::Travel::DE::VRR::Route::Part elements. - -=item $route->parts() - -Returns a list of Net::Travel::DE::VRR::Route::Part(3pm) elements describing -the actual route. - -=back - -=head1 DIAGNOSTICS - -None. - -=head1 DEPENDENCIES - -None. - -=head1 BUGS AND LIMITATIONS - -None known. - -=head1 SEE ALSO - -Net::Travel::DE::VRR(3pm), Net::Travel::DE::VRR::Route::Part(3pm). - -=head1 AUTHOR - -Copyright (C) 2011 by Daniel Friesel Ederf@finalrewind.orgE - -=head1 LICENSE - - 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/lib/Net/Travel/DE/VRR/Route/Part.pm b/lib/Net/Travel/DE/VRR/Route/Part.pm deleted file mode 100644 index a85e96c..0000000 --- a/lib/Net/Travel/DE/VRR/Route/Part.pm +++ /dev/null @@ -1,150 +0,0 @@ -package Net::Travel::DE::VRR::Route::Part; - -use strict; -use warnings; -use 5.010; - -use parent 'Class::Accessor'; - -our $VERSION = '1.3'; - -Net::Travel::DE::VRR::Route::Part->mk_ro_accessors( - qw(arrival_stop arrival_time departure_stop departure_time train_line - train_destination) -); - -sub new { - my ( $obj, %conf ) = @_; - - my $ref = \%conf; - - return bless( $ref, $obj ); -} - -sub extra { - my ($self) = @_; - - return @{ $self->{extra} // [] }; -} - -1; - -__END__ - -=head1 NAME - -Net::Travel::DE::VRR::Route::Part - Describes one connection between two -points, without interchanges - -=head1 SYNOPSIS - - for my $part ( $route->parts() ) { - - if ( $part->extra() ) { - say join( "\n", $part->extra() ); - } - - printf( - "%s at %s -> %s at %s, via %s to %s", - $part->departure_time, $part->departure_stop, - $part->arrival_time, $part->arrival_stop, - $part->train_line, $part->train_destination, - ); - } - -=head1 VERSION - -version 0.3 - -=head1 DESCRIPTION - -B holds one specific connection (without -interchanges) between two points. It specifies the start/stop point and time, -the train line and its destination, and optional additional data. - -It is usually obtained by a call to Net::Travel::DE::VRR::Route(3pm)'s -B method. - -=head1 METHODS - -=over - -=item $part = Net::Travel::DE::VRR::Route::Part->new(I<%data>) - -Creates a new Net::Travel::DE::VRR::Route::Part object. I consists of: - -=over - -=item B => I:I - -Arrival time - -=item B => I - -Arrival stop (city plus station / address) - -=item B => I - -Departure time - -=item B => I - -Departure stop (city plus station / address) - -=item B => I - -Destination of the train connecting the stops - -=item B => I - -The train's line name. - -=item B => B<[> [ I, [ I [ I<...> ] ] ] B<]> - -Additional information about this connection. Array-ref of newline-terminated -strings. - -=back - -=item $part->get(I) - -Returns the value of I (B, B etc., see -B). - -Each of these I also has an accessor. So C<< $part->departure_time() >> -is the same as C<< $part->get('departure_time') >>. - -=item $part->extra() - -Returns a list of additional information about this route part, if provided. -Returns an empty list otherwise. - -=back - -=head1 DIAGNOSTICS - -None. - -=head1 DEPENDENCIES - -=over - -=item * Class::Accessor(3pm) - -=back - -=head1 BUGS AND LIMITATIONS - -None known. - -=head1 SEE ALSO - -Net::Travel::DE::VRR(3pm). - -=head1 AUTHOR - -Copyright (C) 2011 by Daniel Friesel Ederf@finalrewind.orgE - -=head1 LICENSE - - 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/lib/Travel/Routing/DE/VRR.pm b/lib/Travel/Routing/DE/VRR.pm new file mode 100644 index 0000000..c73d033 --- /dev/null +++ b/lib/Travel/Routing/DE/VRR.pm @@ -0,0 +1,712 @@ +package Travel::Routing::DE::VRR; + +use strict; +use warnings; +use 5.010; + +use Carp qw(confess); +use Travel::Routing::DE::VRR::Route; +use LWP::UserAgent; +use XML::LibXML; + +our $VERSION = '1.3'; + +sub set_time { + my ( $self, %conf ) = @_; + + my $time; + + if ( $conf{departure_time} ) { + $self->{post}->{itdTripDateTimeDepArr} = 'dep'; + $time = $conf{departure_time}; + } + elsif ( $conf{arrival_time} ) { + $self->{post}->{itdTripDateTimeDepArr} = 'arr'; + $time = $conf{arrival_time}; + } + else { + confess('time: Specify either departure_time or arrival_time'); + } + + if ( $time !~ / ^ [0-2]? \d : [0-5]? \d $ /x ) { + confess("time: must match HH:MM - '${time}'"); + } + + @{ $self->{post} }{ 'itdTimeHour', 'itdTimeMinute' } = split( /:/, $time ); + + return; +} + +sub departure_time { + my ( $self, $time ) = @_; + + return $self->set_time( departure_time => $time ); +} + +sub arrival_time { + my ( $self, $time ) = @_; + + return $self->set_time( arrival_time => $time ); +} + +sub date { + my ( $self, $date ) = @_; + + my ( $day, $month, $year ) = split( /[.]/, $date ); + + if ( not defined $day or not length($day) or $day < 1 or $day > 31 ) { + confess("date: invalid day, must match DD.MM[.[YYYY]] - '${date}'"); + } + if ( not defined $month or not length($month) or $month < 1 or $month > 12 ) + { + confess("date: invalid month, must match DD.MM[.[YYYY]] - '${date}'"); + } + + if ( not defined $year or not length($year) ) { + $year = ( localtime(time) )[5] + 1900; + } + + @{ $self->{post} }{ 'itdDateDay', 'itdDateMonth', 'itdDateYear' } + = ( $day, $month, $year ); + + return; +} + +sub exclude { + my ( $self, @exclude ) = @_; + + my @mapping = qw{ + zug s-bahn u-bahn stadtbahn tram stadtbus regionalbus + schnellbus seilbahn schiff ast sonstige + }; + + foreach my $exclude_type (@exclude) { + my $ok = 0; + for my $map_id ( 0 .. $#mapping ) { + if ( $exclude_type eq $mapping[$map_id] ) { + $self->{post}->{"inclMOT_${map_id}"} = undef; + $ok = 1; + } + } + if ( not $ok ) { + confess("exclude: Unsupported type '${exclude_type}'"); + } + } + + return; +} + +sub max_interchanges { + my ( $self, $max ) = @_; + + $self->{post}->{maxChanges} = $max; + + return; +} + +sub select_interchange_by { + my ( $self, $prefer ) = @_; + + given ($prefer) { + when ('speed') { $self->{post}->{routeType} = 'LEASTTIME' } + when ('waittime') { $self->{post}->{routeType} = 'LEASTINTERCHANGE' } + when ('distance') { $self->{post}->{routeType} = 'LEASTWALKING' } + default { + confess( +"select_interchange_by: Must be speed/waittime/distance: '${prefer}'" + ); + } + } + + return; +} + +sub train_type { + my ( $self, $include ) = @_; + + given ($include) { + when ('local') { $self->{post}->{lineRestriction} = 403 } + when ('ic') { $self->{post}->{lineRestriction} = 401 } + when ('ice') { $self->{post}->{lineRestriction} = 400 } + default { + confess("train_type: Must be local/ic/ice: '${include}'"); + } + } + + return; +} + +sub use_near_stops { + my ( $self, $toggle ) = @_; + + $self->{post}->{useProxFootSearch} = $toggle; + + return; +} + +sub walk_speed { + my ( $self, $walk_speed ) = @_; + + if ( $walk_speed ~~ [ 'normal', 'fast', 'slow' ] ) { + $self->{post}->{changeSpeed} = $walk_speed; + } + else { + confess("walk_speed: Must be normal/fast/slow: '${walk_speed}'"); + } + + return; +} + +sub with_bike { + my ( $self, $bike ) = @_; + + $self->{post}->{bikeTakeAlong} = $bike; + + return; +} + +sub place { + my ( $self, $which, $place, $stop, $type ) = @_; + + if ( not( $place and $stop ) ) { + confess('place: Need >= three elements'); + } + + $type //= 'stop'; + + @{ $self->{post} }{ "place_${which}", "name_${which}" } = ( $place, $stop ); + + if ( $type ~~ [qw[address poi stop]] ) { + $self->{post}->{"type_${which}"} = $type; + } + + return; +} + +sub create_post { + my ($self) = @_; + + my $conf = $self->{config}; + my @now = localtime( time() ); + + $self->{post} = { + changeSpeed => 'normal', + command => q{}, + execInst => q{}, + imparedOptionsActive => 1, + inclMOT_0 => 'on', + inclMOT_1 => 'on', + inclMOT_10 => 'on', + inclMOT_11 => 'on', + inclMOT_2 => 'on', + inclMOT_3 => 'on', + inclMOT_4 => 'on', + inclMOT_5 => 'on', + inclMOT_6 => 'on', + inclMOT_7 => 'on', + inclMOT_8 => 'on', + inclMOT_9 => 'on', + includedMeans => 'checkbox', + itOptionsActive => 1, + itdDateDay => $now[3], + itdDateMonth => $now[4] + 1, + itdDateYear => $now[5] + 1900, + itdLPxx_ShowFare => q{ }, + itdLPxx_command => q{}, + itdLPxx_enableMobilityRestrictionOptionsWithButton => q{}, + itdLPxx_id_destination => ':destination', + itdLPxx_id_origin => ':origin', + itdLPxx_id_via => ':via', + itdLPxx_mapState_destination => q{}, + itdLPxx_mapState_origin => q{}, + itdLPxx_mapState_via => q{}, + itdLPxx_mdvMap2_destination => q{}, + itdLPxx_mdvMap2_origin => q{}, + itdLPxx_mdvMap2_via => q{}, + itdLPxx_mdvMap_destination => q{::}, + itdLPxx_mdvMap_origin => q{::}, + itdLPxx_mdvMap_via => q{::}, + itdLPxx_priceCalculator => q{}, + itdLPxx_transpCompany => 'vrr', + itdLPxx_view => q{}, + itdTimeHour => $now[2], + itdTimeMinute => $now[1], + itdTripDateTimeDepArr => 'dep', + language => 'de', + lineRestriction => 403, + maxChanges => 9, + nameInfo_destination => 'invalid', + nameInfo_origin => 'invalid', + nameInfo_via => 'invalid', + nameState_destination => 'empty', + nameState_origin => 'empty', + nameState_via => 'empty', + name_destination => q{}, + name_origin => q{}, + name_via => q{}, + placeInfo_destination => 'invalid', + placeInfo_origin => 'invalid', + placeInfo_via => 'invalid', + placeState_destination => 'empty', + placeState_origin => 'empty', + placeState_via => 'empty', + place_destination => q{}, + place_origin => q{}, + place_via => q{}, + ptOptionsActive => 1, + requestID => 0, + routeType => 'LEASTTIME', + sessionID => 0, + text => 1993, + trITArrMOT => 100, + trITArrMOTvalue100 => 8, + trITArrMOTvalue101 => 10, + trITArrMOTvalue104 => 10, + trITArrMOTvalue105 => 10, + trITDepMOT => 100, + trITDepMOTvalue100 => 8, + trITDepMOTvalue101 => 10, + trITDepMOTvalue104 => 10, + trITDepMOTvalue105 => 10, + typeInfo_destination => 'invalid', + typeInfo_origin => 'invalid', + typeInfo_via => 'invalid', + type_destination => 'stop', + type_origin => 'stop', + type_via => 'stop', + useRealtime => 1 + }; + + $self->place( 'origin', @{ $conf->{origin} } ); + $self->place( 'destination', @{ $conf->{destination} } ); + + if ( $conf->{via} ) { + $self->place( 'via', @{ $conf->{via} } ); + } + if ( $conf->{arrival_time} || $conf->{departure_time} ) { + $self->set_time( %{$conf} ); + } + if ( $conf->{date} ) { + $self->date( $conf->{date} ); + } + if ( $conf->{exclude} ) { + $self->exclude( @{ $conf->{exclude} } ); + } + if ( $conf->{max_interchanges} ) { + $self->max_interchanges( $conf->{max_interchanges} ); + } + if ( $conf->{select_interchange_by} ) { + $self->select_interchange_by( $conf->{select_interchange_by} ); + } + if ( $conf->{use_near_stops} ) { + $self->use_near_stops(1); + } + if ( $conf->{train_type} ) { + $self->train_type( $conf->{train_type} ); + } + if ( $conf->{walk_speed} ) { + $self->walk_speed( $conf->{walk_speed} ); + } + if ( $conf->{with_bike} ) { + $self->with_bike(1); + } + + return; +} + +sub parse_initial { + my ($self) = @_; + + my $tree = $self->{tree} + = XML::LibXML->load_html( string => $self->{html_reply}, ); + + my $con_part = 0; + my $con_no; + my $cons = []; + + my $xp_td = XML::LibXML::XPathExpression->new('//table//table/tr/td'); + my $xp_img = XML::LibXML::XPathExpression->new('./img'); + + foreach my $td ( @{ $tree->findnodes($xp_td) } ) { + + my $colspan = $td->getAttribute('colspan') // 0; + my $class = $td->getAttribute('class') // q{}; + + if ( $colspan != 8 and $class !~ /^bgColor2?$/ ) { + next; + } + + if ( $colspan == 8 ) { + if ( $td->textContent() =~ m{ (? \d+ ) [.] .+ Fahrt }x ) { + $con_no = $+{no} - 1; + $con_part = 0; + next; + } + } + + if ( $class =~ /^bgColor2?$/ ) { + if ( $class eq 'bgColor' and ( $con_part % 2 ) == 1 ) { + $con_part++; + } + elsif ( $class eq 'bgColor2' and ( $con_part % 2 ) == 0 ) { + $con_part++; + } + } + + if ( defined $con_no + and not $td->exists($xp_img) + and $td->textContent() !~ /^\s*$/ ) + { + push( @{ $cons->[$con_no]->[$con_part] }, $td->textContent() ); + } + } + + return $cons; +} + +sub parse_pretty { + my ( $self, $con_parts ) = @_; + + my @elements; + my @next_extra; + + for my $con ( @{$con_parts} ) { + + my $hash; + + # Note: Changes @{$con} elements + foreach my $str ( @{$con} ) { + $str =~ s/[\s\n\t]+/ /gs; + $str =~ s/^ //; + $str =~ s/ $//; + } + + if ( @{$con} < 5 ) { + @next_extra = @{$con}; + next; + } + + # @extra may contain undef values + foreach my $extra (@next_extra) { + if ($extra) { + push( @{ $hash->{extra} }, $extra ); + } + } + @next_extra = undef; + + if ( $con->[0] !~ / \d{2} : \d{2} /ox ) { + splice( @{$con}, 0, 0, q{} ); + splice( @{$con}, 4, 0, q{} ); + $con->[7] = q{}; + } + elsif ( $con->[4] =~ / Plan: \s ab /ox ) { + push( @{ $hash->{extra} }, splice( @{$con}, 4, 1 ) ); + } + + foreach my $extra ( splice( @{$con}, 8, -1 ) ) { + push( @{ $hash->{extra} }, $extra ); + } + + $hash->{departure_time} = $con->[0]; + + # always "ab" $con->[1]; + $hash->{departure_stop} = $con->[2]; + $hash->{train_line} = $con->[3]; + $hash->{arrival_time} = $con->[4]; + + # always "an" $con->[5]; + $hash->{arrival_stop} = $con->[6]; + $hash->{train_destination} = $con->[7]; + + push( @elements, $hash ); + } + + return Travel::Routing::DE::VRR::Route->new(@elements); +} + +sub new { + my ( $obj, %conf ) = @_; + + my $ref = {}; + + $ref->{config} = \%conf; + + bless( $ref, $obj ); + + $ref->create_post(); + + return $ref; +} + +sub submit { + my ( $self, %conf ) = @_; + + $conf{autocheck} = 1; + + $self->{ua} = LWP::UserAgent->new(%conf); + + my $response = $self->{ua} + ->post( 'http://efa.vrr.de/vrr/XSLT_TRIP_REQUEST2', $self->{post} ); + + # XXX (workaround) + # The content actually is iso-8859-1. But HTML::Message doesn't actually + # decode character strings when they have that encoding. However, it + # doesn't check for latin-1, which is an alias for iso-8859-1. + + $self->{html_reply} = $response->decoded_content( charset => 'latin-1' ); + + $self->parse(); + + return; +} + +sub parse { + my ($self) = @_; + + my $raw_cons = $self->parse_initial(); + + for my $raw_con ( @{$raw_cons} ) { + push( @{ $self->{routes} }, $self->parse_pretty($raw_con) ); + } + + $self->check_ambiguous(); + $self->check_no_connections(); + + if ( @{$raw_cons} == 0 ) { + confess('Got no data to parse'); + } + + return 1; +} + +sub check_ambiguous { + my ($self) = @_; + my $tree = $self->{tree}; + + my $xp_select = XML::LibXML::XPathExpression->new('//select'); + my $xp_option = XML::LibXML::XPathExpression->new('./option'); + + foreach my $select ( @{ $tree->findnodes($xp_select) } ) { + + my $post_key = $select->getAttribute('name'); + my @possible; + + foreach my $val ( $select->findnodes($xp_option) ) { + push( @possible, $val->textContent() ); + } + my $err_text = join( q{, }, @possible ); + + confess("Ambiguous input for '${post_key}': '${err_text}'"); + } + + return; +} + +sub check_no_connections { + my ($self) = @_; + my $tree = $self->{tree}; + + my $xp_err_img = XML::LibXML::XPathExpression->new( + '//td/img[@src="images/ausrufezeichen.jpg"]'); + + my $err_node = $tree->findnodes($xp_err_img)->[0]; + + if ($err_node) { + my $text = $err_node->parentNode()->parentNode()->textContent(); + confess("Got no connections: '${text}'"); + } + + return; +} + +sub routes { + my ($self) = @_; + + return @{ $self->{routes} }; +} + +1; + +__END__ + +=head1 NAME + +Travel::Routing::DE::VRR - inofficial interface to the efa.vrr.de German itinerary service + +=head1 SYNOPSIS + + use Travel::Routing::DE::VRR; + + my $efa = Travel::Routing::DE::VRR->new( + origin => [ 'Essen', 'HBf' ], + destination => [ 'Duisburg', 'HBf' ], + ); + + $efa->submit(); + + for my $route ( $efa->routes() ) { + for my $part ( $route->parts() ) { + printf( + "%s at %s -> %s at %s, via %s to %s", + $part->departure_time, $part->departure_stop, + $part->arrival_time, $part->arrival_stop, + $part->train_line, $part->train_destination, + ); + } + print "\n\n"; + } + +=head1 VERSION + +version 1.3 + +=head1 DESCRIPTION + +B is a client for the efa.vrr.de web interface. +You pass it the start/stop of your journey, maybe a time and a date and more +details, and it returns the up-to-date scheduled connections between those two +stops. + +It uses B and B for this. + +=head1 METHODS + +=over + +=item $efa = Travel::Routing::DE::VRR->new(I<%conf>) + +Returns a new Travel::Routing::DE::VRR object and sets up its POST data via %conf. + +Valid hash keys and their values are: + +=over + +=item B => B<[> IB<,> I [ B<,> I ] B<]> + +Mandatory. Sets the start of the journey. +I is optional and may be one of B (default), B
(street +and house number) or B ("point of interest"). + +=item B => B<[> IB<,> I [ B<,> I ] B<]> + +Mandatory. Sets the end of the journey, see B. + +=item B => B<[> IB<,> I [ B<,> I ] B<]> + +Optional. Specifies an intermediate stop which the resulting itinerary must +contain. See B for arguments. + +=item B => I + +Journey end time + +=item B => I + +Journey start time. Default: now + +=item B => I[I] + +Journey date. Default: tdoay + +=item B => \@exclude + +Do not use certain transport types for itinerary. Accepted arguments: +zug, s-bahn, u-bahn, stadtbahn, tram, stadtbus, regionalbus, schnellbus, +seilbahn, schiff, ast, sonstige + +=item B => I + +Set maximum number of interchanges + +=item B => B|B|B + +Prefer either fast connections (default), connections with low wait time or +connections with little distance to walk + +=item B => B<0>|B<1> + +If true: Try using near stops instead of the specified origin/destination ones + +=item B => B|B|B + +Include only local trains into itinarery (default), all but ICEs, or all. + +The latter two are usually way more expensive for short routes. + +=item B => B|B|B + +Set walk speed. Default: B + +=item B => B<0>|B<1> + +If true: Prefer connections allowing passengers with bikes + +=back + +=item $efa->submit(I<%opts>) + +Submit the query to B. +I<%opts> is passed on to C<< LWP::UserAgent->new(%opts) >>. + +=item $efa->routes() + +Returns a list of Travel::Routing::DE::VRR::Route(3pm) elements. Each one contains +one method of getting from start to stop. + +=back + +=head2 ACCESSORS + +The following methods act like the arguments to B. See there. + +=over + +=item $efa->departure_time(I<$time>) + +=item $efa->arrival_time(I<$time>) + +=item $efa->date(I<$date>) + +=item $efa->exclude(I<@exclude>) + +=item $efa->max_interchanges(I<$num>) + +=item $efa->select_interchange_by(I<$selection>) + +=item $efa->train_type(I<$type>) + +=item $efa->use_near_stops(I<$bool>) + +=item $efa->walk_speed(I<$speed>) + +=item $efa->with_bike(I<$bool>) + +=back + +=head1 DIAGNOSTICS + +Dies with a backtrace when anything goes wrong. + +=head1 DEPENDENCIES + +=over + +=item * LWP::UserAgent(3pm) + +=item * XML::LibXML(3pm) + +=back + +=head1 BUGS AND LIMITATIONS + +The parser is still somewhat fragile and has no proper error handling. + +It is best not to pass Unicode characters to B. + +=head1 AUTHOR + +Copyright (C) 2009-2011 by Daniel Friesel Ederf@finalrewind.orgE + +=head1 LICENSE + + 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/lib/Travel/Routing/DE/VRR/Route.pm b/lib/Travel/Routing/DE/VRR/Route.pm new file mode 100644 index 0000000..c250be7 --- /dev/null +++ b/lib/Travel/Routing/DE/VRR/Route.pm @@ -0,0 +1,99 @@ +package Travel::Routing::DE::VRR::Route; + +use strict; +use warnings; +use 5.010; + +use Travel::Routing::DE::VRR::Route::Part; + +our $VERSION = '1.3'; + +sub new { + my ( $obj, @parts ) = @_; + + my $ref = {}; + + for my $part (@parts) { + push( + @{ $ref->{parts} }, + Travel::Routing::DE::VRR::Route::Part->new( %{$part} ) + ); + } + + return bless( $ref, $obj ); +} + +sub parts { + my ($self) = @_; + + return @{ $self->{parts} }; +} + +1; + +__END__ + +=head1 NAME + +Travel::Routing::DE::VRR::Route - Single route (connection) between two points + +=head1 SYNOPSIS + + for my $route ( $efa->routes() ) { + for my $part ( $route->parts() ) { + # $part is a Travel::Routing::DE::VRR::Route::Part object + } + } + +=head1 VERSION + +version 1.3 + +=head1 DESCRIPTION + +Travel::Routing::DE::VRR::Route describes a single method of getting from one +point to another. It holds a bunch of Travel::Routing::DE::VRR::Route::Part(3pm) +objects describing the parts of the route in detail. Each part depends on the +previous one. + +You usually want to acces it via C<< $efa->routes() >>. + +=head1 METHODS + +=over + +=item my $route = Travel::Routing::DE::VRR::Route->new(I<@parts>) + +Creates a new Travel::Routing::DE::VRR::Route elements consisting of I, +which are Travel::Routing::DE::VRR::Route::Part elements. + +=item $route->parts() + +Returns a list of Travel::Routing::DE::VRR::Route::Part(3pm) elements describing +the actual route. + +=back + +=head1 DIAGNOSTICS + +None. + +=head1 DEPENDENCIES + +None. + +=head1 BUGS AND LIMITATIONS + +None known. + +=head1 SEE ALSO + +Travel::Routing::DE::VRR(3pm), Travel::Routing::DE::VRR::Route::Part(3pm). + +=head1 AUTHOR + +Copyright (C) 2011 by Daniel Friesel Ederf@finalrewind.orgE + +=head1 LICENSE + + 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/lib/Travel/Routing/DE/VRR/Route/Part.pm b/lib/Travel/Routing/DE/VRR/Route/Part.pm new file mode 100644 index 0000000..e47a512 --- /dev/null +++ b/lib/Travel/Routing/DE/VRR/Route/Part.pm @@ -0,0 +1,150 @@ +package Travel::Routing::DE::VRR::Route::Part; + +use strict; +use warnings; +use 5.010; + +use parent 'Class::Accessor'; + +our $VERSION = '1.3'; + +Travel::Routing::DE::VRR::Route::Part->mk_ro_accessors( + qw(arrival_stop arrival_time departure_stop departure_time train_line + train_destination) +); + +sub new { + my ( $obj, %conf ) = @_; + + my $ref = \%conf; + + return bless( $ref, $obj ); +} + +sub extra { + my ($self) = @_; + + return @{ $self->{extra} // [] }; +} + +1; + +__END__ + +=head1 NAME + +Travel::Routing::DE::VRR::Route::Part - Describes one connection between two +points, without interchanges + +=head1 SYNOPSIS + + for my $part ( $route->parts() ) { + + if ( $part->extra() ) { + say join( "\n", $part->extra() ); + } + + printf( + "%s at %s -> %s at %s, via %s to %s", + $part->departure_time, $part->departure_stop, + $part->arrival_time, $part->arrival_stop, + $part->train_line, $part->train_destination, + ); + } + +=head1 VERSION + +version 0.3 + +=head1 DESCRIPTION + +B holds one specific connection (without +interchanges) between two points. It specifies the start/stop point and time, +the train line and its destination, and optional additional data. + +It is usually obtained by a call to Travel::Routing::DE::VRR::Route(3pm)'s +B method. + +=head1 METHODS + +=over + +=item $part = Travel::Routing::DE::VRR::Route::Part->new(I<%data>) + +Creates a new Travel::Routing::DE::VRR::Route::Part object. I consists of: + +=over + +=item B => I:I + +Arrival time + +=item B => I + +Arrival stop (city plus station / address) + +=item B => I + +Departure time + +=item B => I + +Departure stop (city plus station / address) + +=item B => I + +Destination of the train connecting the stops + +=item B => I + +The train's line name. + +=item B => B<[> [ I, [ I [ I<...> ] ] ] B<]> + +Additional information about this connection. Array-ref of newline-terminated +strings. + +=back + +=item $part->get(I) + +Returns the value of I (B, B etc., see +B). + +Each of these I also has an accessor. So C<< $part->departure_time() >> +is the same as C<< $part->get('departure_time') >>. + +=item $part->extra() + +Returns a list of additional information about this route part, if provided. +Returns an empty list otherwise. + +=back + +=head1 DIAGNOSTICS + +None. + +=head1 DEPENDENCIES + +=over + +=item * Class::Accessor(3pm) + +=back + +=head1 BUGS AND LIMITATIONS + +None known. + +=head1 SEE ALSO + +Travel::Routing::DE::VRR(3pm). + +=head1 AUTHOR + +Copyright (C) 2011 by Daniel Friesel Ederf@finalrewind.orgE + +=head1 LICENSE + + 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/t/20-vrr.t b/t/20-vrr.t index 96a8c69..c97b376 100644 --- a/t/20-vrr.t +++ b/t/20-vrr.t @@ -6,9 +6,9 @@ use 5.010; use Test::More tests => 59; BEGIN { - use_ok('Net::Travel::DE::VRR'); + use_ok('Travel::Routing::DE::VRR'); } -require_ok('Net::Travel::DE::VRR'); +require_ok('Travel::Routing::DE::VRR'); sub efa_conf { my $ret = { @@ -23,7 +23,7 @@ sub efa_conf { sub efa_new { return new_ok( - 'Net::Travel::DE::VRR' => [%{efa_conf(@_)}] + 'Travel::Routing::DE::VRR' => [%{efa_conf(@_)}] ); } -- cgit v1.2.3