randomfixin
[ottawa-travel-planner.git] / Planner.py
blob5e0ef335479d99f0e42f9e107d26235f84e635ae
2 # vi: set softtabstop=4 shiftwidth=4 tabstop=8 expandtab:
4 """A frontend for the OC Transpo Travel Planner."""
6 import cookielib
7 import urllib2
8 import urllib
9 import sre
11 import Itinerary
13 def plan(start, end, time):
14 """Plans a route between two Locations at a certain PlanTime."""
15 planner = TravelPlannerClient()
16 planner.feedStartLocation(start)
17 planner.feedEndLocation(end)
18 html = planner.feedTime(time)
19 return Itinerary.Itinerary(start, end, time, html)
21 class TravelPlannerException(Exception):
22 """Generic parent exception for anything thrown by us, as opposed to
23 things thrown by the network layer."""
25 def __init__(self, value):
26 Exception.__init__(self, value)
28 class InvalidLocationException(TravelPlannerException):
29 """Thrown when a source or destination can't be found.
31 The "args" value of the exception is the original Location."""
33 def _init(self, location):
34 TravelPlannerException.__init__(self, location)
36 class TravelPlannerClient:
37 def __init__(self):
38 # Set up a cookie-aware client.
39 self.cj = cookielib.CookieJar()
40 self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cj))
42 # Initialize the session.
43 r = self._sendRequest(self.START_PAGE, None)
45 def feedStartLocation(self, loc):
46 # name="tp_fromAddress" method="get" action="FromAddress.oci"
47 # fromAddress text
48 # streetType=Other
49 params = (("fromAddress", loc), ("streetType", "Other"))
50 r = self._sendRequest("FromAddress.oci", params)
52 def feedEndLocation(self, loc):
53 params = (("toAddress", loc), ("streetType", "Other"))
54 r = self._sendRequest("ToAddress.oci", params)
56 def feedTime(self, time):
57 # name="tp_time" action="SelectTime.oci"
58 r = self._sendRequest("SelectTime.oci", time.toPlannerParams())
60 return r
62 def _sendRequest(self, page, params):
63 url = self.URL_BASE + page
64 if params is not None:
65 url += "?" + urllib.urlencode(params)
67 response = self.opener.open(url)
68 self._checkObviousBadness(response)
70 html = self._grabLimitedResponse(response)
71 self._scanForError(html)
72 return html
74 def _checkObviousBadness(self, response):
75 if response.code != 200:
76 raise TravelPlannerException("Got HTTP " + response.code
77 + " error from server")
78 if "errorPage.oci" in response.geturl():
79 raise TravelPlannerException("Redirected to error page")
81 def _grabLimitedResponse(self, response):
82 count = 0
84 accum = ""
85 for line in response:
86 count += len(line)
87 if (count <= self.RESPONSE_SIZE_LIMIT):
88 accum += line
89 else:
90 break
91 return accum
95 # Regular expressions: probably the worst way to parse HTML
96 # Probably the best thing to put in your breakfast cereal.
98 def _scanForError(self, text):
99 match = _error_rx.search(text)
100 if match:
101 raise TravelPlannerException(match.group("msg"))
103 URL_BASE = "http://www.octranspo.com/tps/jnot/"
104 START_PAGE = "startEN.oci"
105 RESPONSE_SIZE_LIMIT = 100000
109 # Scan for an error.
110 # <table cellpadding="0" cellspacing="0" summary="Warning message" class="warning" width="85%">
111 # <tr>
112 # <td><img src="tripPlanning/images/imgWarning.gif"></td>
113 # <td>The address you specified was not found. Please enter another.</td>
114 # </tr>
115 # </table>
116 _error_re = ('<table[^>]*class="(?:warning|error)[^>]*>\s*'
117 '<tr>(?:\s*<td>\s*<img[^>]*>\s*</td>)?'
118 # ?s: DOTALL: . matches \n
119 # *? is non-greedy
120 '\s*<td>(?s)\s*(?P<msg>[\d\D]*?)\s*</td>')
121 _error_rx = sre.compile(_error_re)