2 # vi: set softtabstop=4 shiftwidth=4 tabstop=8 expandtab:
4 """A frontend for the OC Transpo Travel Planner."""
11 def plan(start
, end
, time
):
12 """Plans a route between two Locations at a certain PlanTime."""
13 planner
= TravelPlannerClient()
14 planner
.feedStartLocation(start
)
15 planner
.feedEndLocation(end
)
16 planner
.feedTime(time
)
18 class TravelPlannerException(Exception):
19 """Generic parent exception for anything thrown by us, as opposed to
20 things thrown by the network layer."""
22 def _init_(self
, value
):
23 Exception._init
_(self
, value
)
25 class InvalidLocationException(TravelPlannerException
):
26 """Thrown when a source or destination can't be found.
28 The "args" value of the exception is the original Location."""
30 def _init(self
, location
):
31 TravelPlannerException
._init
_(self
, location
)
33 class TravelPlannerClient
:
35 # Set up a cookie-aware client.
36 self
.cj
= cookielib
.CookieJar()
37 self
.opener
= urllib2
.build_opener(urllib2
.HTTPCookieProcessor(self
.cj
))
39 # Initialize the session.
40 r
= self
._sendRequest
(self
.START_PAGE
, None)
42 def feedStartLocation(self
, loc
):
43 # name="tp_fromAddress" method="get" action="FromAddress.oci"
46 params
= (("fromAddress", loc
), ("streetType", "Other"))
47 r
= self
._sendRequest
("FromAddress.oci", params
)
49 def feedEndLocation(self
, loc
):
50 params
= (("toAddress", loc
), ("streetType", "Other"))
51 r
= self
._sendRequest
("ToAddress.oci", params
)
53 def feedTime(self
, time
):
54 # name="tp_time" action="SelectTime.oci"
56 # requestCode=3 for must leave after
59 r
= self
._sendRequest
("SelectTime.oci", time
.toPlannerParams())
64 def _sendRequest(self
, page
, params
):
65 url
= self
.URL_BASE
+ page
66 if params
is not None:
67 url
+= "?" + urllib
.urlencode(params
)
70 response
= self
.opener
.open(url
)
71 self
._checkObviousBadness
(response
)
73 html
= self
._grabLimitedResponse
(response
)
74 self
._scanForError
(html
)
77 def _checkObviousBadness(self
, response
):
78 if response
.code
!= 200:
79 raise TravelPlannerException("Got HTTP " + response
.code
80 + " error from server")
81 if "errorPage.oci" in response
.geturl():
82 raise TravelPlannerException("Redirected to error page")
84 def _grabLimitedResponse(self
, response
):
90 if (count
<= self
.RESPONSE_SIZE_LIMIT
):
98 # Regular expressions: probably the worst way to parse HTML
99 # Probably the best thing to put in your breakfast cereal.
101 def _scanForError(self
, text
):
102 match
= error_rx
.search(text
)
104 raise TravelPlannerException(match
.group("msg"))
106 URL_BASE
= "http://www.octranspo.com/tps/jnot/"
107 START_PAGE
= "startEN.oci"
108 RESPONSE_SIZE_LIMIT
= 100000
113 # <table cellpadding="0" cellspacing="0" summary="Warning message" class="warning" width="85%">
115 # <td><img src="tripPlanning/images/imgWarning.gif"></td>
116 # <td>The address you specified was not found. Please enter another.</td>
119 _error_re
= ('<table[^>]*class="(?:warning|error)[^>]*>\s*'
120 '<tr>(?:\s*<td>\s*<img[^>]*>\s*</td>)?'
121 # ?s: DOTALL: . matches \n
123 '\s*<td>(?s)\s*(?P<msg>[\d\D]*?)\s*</td>')
124 _error_rx
= sre
.compile(_error_re
)