package Travel::Status::DE::DeutscheBahn; use strict; use warnings; use 5.010; no if $] >= 5.018, warnings => "experimental::smartmatch"; use Carp qw(confess); use LWP::UserAgent; use POSIX qw(strftime); use Travel::Status::DE::DeutscheBahn::Result; use XML::LibXML; our $VERSION = '1.05'; sub new { my ( $obj, %conf ) = @_; my $date = strftime( '%d.%m.%Y', localtime(time) ); my $time = strftime( '%H:%M', localtime(time) ); my %lwp_options = %{ $conf{lwp_options} // { timeout => 10 } }; my $ua = LWP::UserAgent->new(%lwp_options); $ua->env_proxy; my $reply; my $lang = $conf{language} // 'd'; if ( not $conf{station} ) { confess('You need to specify a station'); } my $ref = { mot_filter => [ $conf{mot}->{ice} // 1, $conf{mot}->{ic_ec} // 1, $conf{mot}->{d} // 1, $conf{mot}->{nv} // 1, $conf{mot}->{s} // 1, $conf{mot}->{bus} // 0, $conf{mot}->{ferry} // 0, $conf{mot}->{u} // 0, $conf{mot}->{tram} // 0, ], post => { advancedProductMode => q{}, input => $conf{station}, date => $conf{date} || $date, time => $conf{time} || $time, REQTrain_name => q{}, start => 'yes', boardType => $conf{mode} // 'dep', # L => 'vs_java3', }, }; for my $i ( 0 .. @{ $ref->{mot_filter} } ) { if ( $ref->{mot_filter}->[$i] ) { $ref->{post}->{"GUIREQProduct_$i"} = 'on'; } } bless( $ref, $obj ); $reply = $ua->post( "http://reiseauskunft.bahn.de/bin/bhftafel.exe/${lang}n?rt=1", $ref->{post} ); if ( $reply->is_error ) { $ref->{errstr} = $reply->status_line(); return $ref; } $ref->{html} = $reply->content; $ref->{tree} = XML::LibXML->load_html( string => $ref->{html}, recover => 2, suppress_errors => 1, suppress_warnings => 1, ); $ref->check_input_error(); return $ref; } sub new_from_html { my ( $obj, %opt ) = @_; my $ref = { html => $opt{html}, post => { boardType => $opt{mode} // 'dep' } }; $ref->{post}->{boardType} = $opt{mode} // 'dep'; $ref->{tree} = XML::LibXML->load_html( string => $ref->{html}, recover => 2, suppress_errors => 1, suppress_warnings => 1, ); return bless( $ref, $obj ); } sub check_input_error { my ($self) = @_; my $xp_errdiv = XML::LibXML::XPathExpression->new( '//div[@class = "errormsg leftMargin"]'); my $xp_opts = XML::LibXML::XPathExpression->new('//select[@class = "error"]'); my $xp_values = XML::LibXML::XPathExpression->new('./option'); my $e_errdiv = ( $self->{tree}->findnodes($xp_errdiv) )[0]; my $e_opts = ( $self->{tree}->findnodes($xp_opts) )[0]; if ($e_errdiv) { $self->{errstr} = $e_errdiv->textContent; if ($e_opts) { my @nodes = ( $e_opts->findnodes($xp_values) ); $self->{errstr} .= join( q{}, map { "\n" . $_->textContent } @nodes ); } } return; } sub errstr { my ($self) = @_; return $self->{errstr}; } sub get_node { my ( $parent, $name, $xpath, $index ) = @_; $index //= 0; my @nodes = $parent->findnodes($xpath); if ( $#nodes < $index ) { # called by map, so we must explicitly return undef. ## no critic (Subroutines::ProhibitExplicitReturnUndef) return undef; } my $node = $nodes[$index]; return $node->textContent; } sub results { my ($self) = @_; my $mode = $self->{post}->{boardType}; my $xp_element = XML::LibXML::XPathExpression->new( "//table[\@class = \"result stboard ${mode}\"]/tr"); my $xp_train_more = XML::LibXML::XPathExpression->new('./td[3]/a'); # bhftafel.exe is not y2k1-safe my $re_morelink = qr{ date = (? .. [.] .. [.] .. ) }x; my @parts = ( [ 'time', './td[@class="time"]' ], [ 'train', './td[3]' ], [ 'route', './td[@class="route"]' ], [ 'dest', './td[@class="route"]//a' ], [ 'platform', './td[@class="platform"]' ], [ 'info', './td[@class="ris"]' ], [ 'routeinfo', './td[@class="route"]//span[@class="red bold"]' ], ); @parts = map { [ $_->[0], XML::LibXML::XPathExpression->new( $_->[1] ) ] } @parts; my $re_via = qr{ ^ \s* (? .+? ) \s* \n (?