2 # vi: set softtabstop=4 shiftwidth=4 tabstop=8 expandtab:
4 """A frontend for the OC Transpo Travel Planner."""
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
:
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"
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())
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
)
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
):
87 if (count
<= self
.RESPONSE_SIZE_LIMIT
):
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
)
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
110 # <table cellpadding="0" cellspacing="0" summary="Warning message" class="warning" width="85%">
112 # <td><img src="tripPlanning/images/imgWarning.gif"></td>
113 # <td>The address you specified was not found. Please enter another.</td>
116 _error_re
= ('<table[^>]*class="(?:warning|error)[^>]*>\s*'
117 '<tr>(?:\s*<td>\s*<img[^>]*>\s*</td>)?'
118 # ?s: DOTALL: . matches \n
120 '\s*<td>(?s)\s*(?P<msg>[\d\D]*?)\s*</td>')
121 _error_rx
= sre
.compile(_error_re
)