1 # -*- coding: UTF-8 -*-
3 import os, sys, time, re
4 import urllib, urllib2, urlparse, cgi
5 from urllib2 import URLError
8 from timetable_parser import StanTimetableParser
12 RSS_URL = 'http://www.reseau-stan.com/rss'
14 get_params = [ 'rub_code', 'gpl_id', 'thm_id', 'lineid', 'date', 'hour', 'direction' ]
20 'name': u"Nancy République / Bouxières-aux-Dames",
26 'name': u"Vandoeuvre Vᅢᄅlodrome / Chaligny",
32 'name': u"Nancy République / Frouard Le Nid",
38 'name': u"Vandoeuvre Vélodrome / Pont Saint-Vincent",
44 'name': u"Nancy République / Pompey Les Vannes",
50 'name': u"Vandoeuvre Vélodrome / Messein",
56 'name': u"Nancy République / Pompey Fonds de Lavaux",
62 'name': u"Nancy Place Carnot / Chaligny",
64 'fg-color': '#a3b909',
68 'name': u"Nancy République / Saint-Nicolas Le Nid",
74 'name': u"Nancy Place Carnot / Pont Saint-Vincent",
76 'fg-color': '#c00266',
80 'name': u"Nancy République / Dombasle Maroc",
86 'name': u"Nancy Place Carnot / Bainville-sur-Madon",
88 'fg-color': '#81d0e3',
92 'name': u"Nancy République / Saint-Nicolas République",
98 'name': u"Nancy République / Dombasle Stand",
101 'bg-color': '#fe6612'
104 'name': u"Nancy République / Champigneulles",
107 'bg-color': '#d3b6d1'
110 'name': u"Vandoeuvre Parc des Expositions / Nancy République",
113 'bg-color': '#92bbad'
121 'name': u"Vandoeuvre CHU Brabois / Essey Mouzimpré",
122 'numbers': ["Tram 1"],
124 'bg-color': '#ff0000'
127 'name': u"Vandoeuvre CHU Brabois / Centre d\'accueil - Villers les Essarts",
128 'numbers': ["Ptit Stan Vilers"],
132 'name': u"Vandoeuvre CHU Brabois / Vandoeuvre Nations - Médecine préventive ",
133 'numbers': ["Ptit Stan Vandoeuvre"],
137 'name': u"Laxou Sapinière / Laxou Provinces",
138 'numbers': ["Ptit Stan Laxou"],
142 'name': u"Nancy Carnot",
143 'numbers': ["Ptit Stan Colline"],
147 'name': u"Malzéville Sadi Carnot - Nancy Cimetière du Sud",
148 'numbers': ["Minibus"],
157 'name': u"Maxéville Champ le Boeuf - Nancy Haut du Lièvre / Jarville Sion",
158 'numbers': ["111", "112"],
160 'bg-color': '#71228d'
163 'name': u"Houdemont / Vandoeuvre Charmois",
166 'bg-color': '#b6a6d3'
169 'name': u"Ludres / Villers Lycée Stanislas",
172 'bg-color': '#705aac'
175 'name': u"Fléville Fleurychamp - Heillecourt / Villers Lycée Stanislas",
178 'bg-color': '#755eac'
181 'name': u"Nancy Beauregard / Nancy République",
184 'bg-color': '#339d2f'
187 'name': u"Villers Clairlieu / Nancy République",
188 'numbers': ["122", "126"],
190 'bg-color': '#9cca1f'
193 'name': u"Vandoeuvre Nations / Nancy République",
196 'bg-color': '#a9d72f'
199 'name': u"Laxou Mouzon / Nancy République",
202 'bg-color': '#39a63b'
206 'name': u"Laxou Provinces / Seichamps",
209 'bg-color': '#048a96'
212 'name': u"Maxéville Mairie / Jarville Sion - Heillecourt - Fléville De la Noue",
213 'numbers': ["131", "132", "133"],
215 'bg-color': '#82cee6'
218 'name': u"Villers Lycée Stanislas / Maxéville Lafayette - Malzéville Savlons",
219 'numbers': ["134", "135"],
221 'bg-color': '#1693cb'
224 'name': u"Nancy Cours Léopold / Laneuveville - La Madeleine",
225 'numbers': ["136", "137"],
227 'bg-color': '#6f88c1'
230 'name': u"Laxou Champ le Boeuf / Vandoeurvre Roberval",
233 'bg-color': '#123e97'
236 'name': u"Nancy Cours Léopold / Ludres",
239 'bg-color': '#6f87c7'
242 'name': u"Malzéville / Saulxures Forêt - Art sur Meurthe",
243 'numbers': ["141", "142"],
245 'bg-color': '#ff750e'
248 'name': u"Malzéville Pixérécourt / Saulxures",
251 'bg-color': '#ffff33'
254 'name': u"Essey C.E.S / Gérard Barrois Stade Marcel Picot",
257 'bg-color': '#f59638'
260 'name': u"Dommartemont / Jarville Sion",
263 'bg-color': '#fad956'
266 'name': u"Essey Mouzimpré / Seichamps - Pulnoy (Lignes accessibles PMR)",
267 'numbers': ["171", "172"],
268 'bg-color': '#f38ab3'
272 def schedules_cache_dirname(self):
273 pystan_schedules = os.path.expanduser('~/.pystan/cache/schedules')
274 if not os.path.isdir(pystan_schedules):
275 #os.makedirs('~/.pystan/cache/schedules')
277 return pystan_schedules
279 def filter_params(self, params):
282 if key in self.get_params:
283 only_wanted[key] = params[key]
286 def schedule_cache_filename(self, params):
287 params['date'] = self.schedules_cache_dateformat(params['date'])
288 params = [ str(key)+"="+str(params[key]) for key in iter(params)]
290 filename = '_'.join(params)
291 filename = filename.replace('/', '-')
292 return "%s/%s" % (self.schedules_cache_dirname(), filename)
294 def schedules_cache_dateformat(self, date):
295 date = time.strptime(date, "%d/%m/%Y")
296 day_n = int(time.strftime("%w", date))
298 if day_n == 0 or day_n == 6:
299 date = time.strftime("%a/%m/%Y", date)
302 date = time.strftime("week/%m/%Y", date)
306 def default_timetable_params(self, params):
307 for param, default in { 'rub_code':6, 'thm_id':6, 'gpl_id':0 }.items(): #{'sens':1, 'date':'01/01/01', 'hour':'12', 'index':'1'}:
308 if not params.has_key(param) or params[param] is None:
309 params[param] = default
312 def search(self, from_location, from_city, to_location, to_city, datetime, restrict=68):
313 cached_result = self.cached_search(from_location, from_city, to_location, to_city, datetime, restrict)
314 if cached_search is not None:
318 POST to http://www.reseau-stan.com/ri/index.asp
319 from_location: keywordsDep
321 to_location: keywordsArr
323 date: laDate (11/03/2009)
324 hour: lHeure (13 = 13h)
326 restrict: typeDate (68 = partir apres, 65 = arriver avant)
330 def cached_search(self, from_location, from_city, to_location, to_city, datetime, restrict=68):
331 cache_id = '-'.join([ from_location, from_city, to_location, to_city, datetime, restrict ])
334 def load_timetable(self, lineid, params=None, bypass=False):
335 params = self.default_timetable_params(params)
336 params['lineid'] = lineid
337 params = self.filter_params(params)
339 timetable_raw_html_file = None
341 if timetable_raw_html_file is None:
342 timetable_raw_html_file = self.load_timetable_local(params, bypass)
344 if timetable_raw_html_file is None:
345 timetable_raw_html_file = self.load_timetable_remote(params)
347 if timetable_raw_html_file is None:
348 print "WARNING:\tNo cache file and no internet connection"
350 if timetable_raw_html_file:
351 parser = StanTimetableParser()
352 parser.feed(timetable_raw_html_file)
354 parsed = parser.result
356 if parsed['navigation'].has_key('next'):
357 index_next = urlparse.urlsplit(parsed['navigation']['next'])[3]
358 index_next = cgi.parse_qs(index_next)
359 parsed['navigation']['next'] = index_next['index'][0]
361 if parsed['navigation'].has_key('prev'):
362 index_prev = urlparse.urlsplit(parsed['navigation']['prev'])[3]
363 index_prev = cgi.parse_qs(index_prev)
364 parsed['navigation']['prev'] = index_prev['index'][0]
366 if len(parsed['stations']) > 0:
367 parsed['stations'] = self.extract_stations_infos(parsed['stations'])
373 return timetable_raw_html_file
375 def load_timetable_remote(self, params):
376 url = 'http://www.reseau-stan.com/horaires_ligne/index.asp'
378 get_params = params.copy()
379 get_params['lign_id'] = get_params['lineid']; del(get_params['lineid'])
381 if get_params.has_key('direction') and int(get_params['direction']) > 0:
382 get_params['sens'] = 2
383 del(get_params['direction'])
385 get_params['sens'] = 1
387 if get_params.has_key('hour'):
388 get_params['index'] = get_params['hour']
389 del(get_params['hour'])
391 req = urllib2.Request(url, urllib.urlencode(get_params))
393 print "GET FROM:\t%s?%s" % (url, urllib.urlencode(get_params))
398 response = urllib2.urlopen(req)
399 file_data = response.read()
401 print "WARNING:\t%s" % e
403 if file_data is not None:
404 self.save_timetable_local(file_data, params)
407 def load_timetable_local(self, params, bypass=False):
408 file = self.schedule_cache_filename(params)
412 print "GET FROM:\t%s" % file
414 if not os.path.isfile(file):
415 #print "WARNING: %s does not exists" % file
418 elif bypass or os.stat(file).st_mtime <= int(time.time()) + max_age:
419 response = open(file)
421 file_data = response.read()
425 print "WARNING:\t%s exists but is too old" % file
430 def save_timetable_local(self, file_data, params):
432 file_data = re.compile('(<div id="goatResult">.*)<div id="footer">', re.DOTALL).findall(file_data)
434 if len(file_data) != 1:
435 print "ERROR:\tCannot store a valid html section (%s occurences found)" % len(file_data)
438 file_data = file_data[0]
440 file = self.schedule_cache_filename(params)
441 print "WRITING:\t%s" % file
442 file_dest = open(file, 'w')
443 file_dest.write(file_data)
447 def extract_stations_infos(self, stations_urls):
449 for station_url in stations_urls:
450 station_id = urlparse.urlsplit(station_url)[3]
451 station_id = cgi.parse_qs(station_id)
453 if not station_id.has_key('pa_id1') or len(station_id['pa_id1']) != 1:
454 print "ERROR:\tCannot extract pa_id1 from url %s " % station_url
457 station_id = station_id['pa_id1'][0]
459 url = 'http://www.reseau-stan.com/ri/cartegoogle.asp'
462 req = urllib2.Request(url, urllib.urlencode({ 'pa_iad1':station_id }))
463 print "GET FROM:\t%s?%s" % (url, urllib.urlencode(get_params))
467 response = urllib2.urlopen(req)
468 file_data = response.read()
470 print "WARNING: %s" % e
472 stations_ids.append({
474 'href': "http://www.reseau-stan.com/ri/cartegoogle.asp?%s" % urllib.urlencode({ 'pa_iad1':station_id })