Class: NationalRail::JourneyPlanner

Inherits:
Object
  • Object
show all
Defined in:
lib/national-rail/journey_planner.rb,
lib/national-rail/journey_planner/details_page_parser.rb

Defined Under Namespace

Classes: DetailsPageParser, NokogiriParser, SummaryRow, TimeParser

Constant Summary collapse

OPERATORS =
{
  "Arriva Trains Wales" => "AW",
  "c2c" => "CC",
  "Chiltern Railways" => "CH",
  "CrossCountry" => "XC",
  "East Coast" => "GR",
  "East Midlands Trains" => "EM",
  "First Capital Connect" => "FC",
  "First Great Western" => "GW",
  "First ScotRail" => "SR",
  "Gatwick Express" => "GX",
  "Grand Central Railway" => "GC",
  "Heathrow Connect" => "HC",
  "Heathrow Express" => "HX",
  "Hull Trains" => "HT",
  "Island Line" => "IL",
  "London Midland" => "LM",
  "London Overground" => "LO",
  "London Underground" => "LT",
  "Merseyrail" => "ME",
  "Northern Rail" => "NT",
  "NXEA" => "LE",
  "South West Trains" => "SW",
  "Southeastern" => "SE",
  "Southern" => "SN",
  "Transpennine Express" => "TP",
  "Virgin Trains" => "VT",
  "Wrexham and Shropshire Railway" => "WS"
}

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeJourneyPlanner

Returns a new instance of JourneyPlanner.


129
130
131
132
133
134
135
# File 'lib/national-rail/journey_planner.rb', line 129

def initialize
  Time.zone ||= 'London'
  @agent = Mechanize.new
  @agent.pluggable_parser.html = NokogiriParser
  aliases = Mechanize::AGENT_ALIASES.keys - %w(Mechanize)
  @agent.user_agent_alias = aliases[rand(aliases.length)]
end

Class Attribute Details

.capture_pathObject

Returns the value of attribute capture_path


57
58
59
# File 'lib/national-rail/journey_planner.rb', line 57

def capture_path
  @capture_path
end

Class Method Details

.capture(page, filename) ⇒ Object


58
59
60
61
62
63
64
65
66
# File 'lib/national-rail/journey_planner.rb', line 58

def capture(page, filename)
  if capture_path.present?
    FileUtils.mkdir_p(capture_path)
    path = File.join(capture_path, filename)
    File.open(path, "w") { |f| f.write(TidyFFI::Tidy.new(page.parser.to_html).clean) }
  end
rescue => e
  puts e
end

Instance Method Details

#plan(options = {}) ⇒ Object


137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/national-rail/journey_planner.rb', line 137

def plan(options = {})
  summary_rows = []
  @agent.get("http://www.nationalrail.co.uk/") do |home_page|
    JourneyPlanner.capture(home_page, "index.html")
    button = nil
    times_page = home_page.form_with(:action => "http://ojp.nationalrail.co.uk/en/s/planjourney/plan") do |form|
      button = form.buttons.last
      form["jpState"] = "single"
      form["commandName"] = "journeyPlannerCommand"
      form["from.searchTerm"] = options[:from]
      form["to.searchTerm"] = options[:to]
      form["timeOfOutwardJourney.arrivalOrDeparture"] = "DEPART"
      form["timeOfOutwardJourney.monthDay"] = options[:time].strftime("%d/%m/%Y")
      form["timeOfOutwardJourney.hour"] = options[:time].strftime("%H")
      form["timeOfOutwardJourney.minute"] = options[:time].strftime("%M")
      # form["timeOfReturnJourney.arrivalOrDeparture"] = "DEPART"
      # form["timeOfReturnJourney.monthDay"] = "Today"
      # form["timeOfReturnJourney.hour"] = "15"
      # form["timeOfReturnJourney.minute"] = "0"
      form["viaMode"] = "VIA"
      form["via.searchTerm"] = ""
      form["offSetOption"] = "0"
      form["reduceTransfers"] = "true"
      form["_reduceTransfers"] = "on" # hidden (duplicate of reduceTransfers?)
      form["operatorMode"] = "SHOW" # alternative is "DONT_SHOW"
      form["operator.code"] = ""
      form["lookForSleeper"] = "true"
      form["_lookForSleeper"] = "on" # hidden (duplicate of lookForSleeper?)
      form["directTrains"] = "true"
      form["_directTrains"] = "on" # hidden (duplicate of directTrains?)
      form["includeOvertakenTrains"] = "true"
      form["_includeOvertakenTrains"] = "on" # hidden (duplicate of includeOvertakenTrains?)
    end.click_button(button)

    JourneyPlanner.capture(times_page, "summary.html")

    if (times_page.doc/".error-message").any?
      raise InputError.new((times_page.doc/".error-message").first.inner_text.gsub(/\s+/, " ").strip)
    end

    year = options[:time].year
    date_as_string = (times_page.doc/".journey-details span").first.children.first.inner_text.gsub(/\s+/, " ").gsub(/\+ 1 day/, '').strip
    date = Date.parse("#{date_as_string} #{year}")

    (times_page.doc/"table#outboundJourneyTable > tbody > tr").reject { |tr| %w(status changes).include?(tr.attributes["class"].value) }.each do |tr|

      if (tr.attributes["class"].value == "day-heading")
        date_as_string = (tr/"th > p > span").first.inner_text.strip
        date = Date.parse("#{date_as_string} #{year}")
        next
      end

      departure_time = TimeParser.new(date).time((tr/"td:nth-child(1)").inner_text.strip)
      number_of_changes = (tr/"td:nth-child(6)").inner_text.strip
      status = (tr/"td:nth-child(10) .status").inner_text.strip

      anchor = (tr/"a[@id^=journeyOption]").first
      next unless anchor

      link = times_page.links.detect { |l| l.attributes["id"] == anchor.attributes["id"].value }

      summary_rows << SummaryRow.new(@agent, date, departure_time, number_of_changes, link, status)
    end
  end
  summary_rows
rescue => e
  page = @agent.current_page
  JourneyPlanner.capture(page, "summary-error.html")
  page_html = TidyFFI::Tidy.new(page.parser.to_html).clean
  raise ParseError.new(e, page_html)
end