--- /dev/null
+# -*- coding: UTF-8 -*-
+
+import os, sys, time, re
+import urllib, urllib2, urlparse, cgi
+from urllib2 import URLError
+
+
+from timetable_parser import StanTimetableParser
+
+class STAN:
+
+ RSS_URL = 'http://www.reseau-stan.com/rss'
+
+ get_params = [ 'rub_code', 'gpl_id', 'thm_id', 'lineid', 'date', 'hour', 'direction' ]
+
+ def lines(self):
+
+ return [
+ { 'id': 144,
+ 'name': u"Nancy République / Bouxières-aux-Dames",
+ 'numbers': ["322"],
+ 'fg-color': '#fff',
+ 'bg-color': '#6ecbf1'
+ },
+ { 'id': 145,
+ 'name': u"Vandoeuvre Vᅢᄅlodrome / Chaligny",
+ 'numbers': ["511"],
+ 'fg-color': '#fff',
+ 'bg-color': '#a3b909'
+ },
+ { 'id': 146,
+ 'name': u"Nancy République / Frouard Le Nid",
+ 'numbers': ["323"],
+ 'fg-color': '#fff',
+ 'bg-color': '#f5a3c8'
+ },
+ { 'id': 147,
+ 'name': u"Vandoeuvre Vélodrome / Pont Saint-Vincent",
+ 'numbers': ["512"],
+ 'fg-color': '#fff',
+ 'bg-color': '#c00266'
+ },
+ { 'id': 148,
+ 'name': u"Nancy République / Pompey Les Vannes",
+ 'numbers': ["324"],
+ 'fg-color': '#fff',
+ 'bg-color': '#d80b8c'
+ },
+ { 'id': 149,
+ 'name': u"Vandoeuvre Vélodrome / Messein",
+ 'numbers': ["514"],
+ 'fg-color': '#fff',
+ 'bg-color': '#006d2c'
+ },
+ { 'id': 150,
+ 'name': u"Nancy République / Pompey Fonds de Lavaux",
+ 'numbers': ["325"],
+ 'fg-color': '#fff',
+ 'bg-color': '#b4d8be'
+ },
+ { 'id': 151,
+ 'name': u"Nancy Place Carnot / Chaligny",
+ 'numbers': ["521"],
+ 'fg-color': '#a3b909',
+ 'bg-color': '#fff'
+ },
+ { 'id': 152,
+ 'name': u"Nancy République / Saint-Nicolas Le Nid",
+ 'numbers': ["621"],
+ 'fg-color': '#fff',
+ 'bg-color': '#0b0a4b'
+ },
+ { 'id': 153,
+ 'name': u"Nancy Place Carnot / Pont Saint-Vincent",
+ 'numbers': ["522"],
+ 'fg-color': '#c00266',
+ 'bg-color': '#fff'
+ },
+ { 'id': 154,
+ 'name': u"Nancy République / Dombasle Maroc",
+ 'numbers': ["624"],
+ 'fg-color': '#fff',
+ 'bg-color': '#fb1518'
+ },
+ { 'id': 155,
+ 'name': u"Nancy Place Carnot / Bainville-sur-Madon",
+ 'numbers': ["523"],
+ 'fg-color': '#81d0e3',
+ 'bg-color': '#fff'
+ },
+ { 'id': 157,
+ 'name': u"Nancy République / Saint-Nicolas République",
+ 'numbers': ["622"],
+ 'fg-color': '#fff',
+ 'bg-color': '#0d97a8'
+ },
+ { 'id': 156,
+ 'name': u"Nancy République / Dombasle Stand",
+ 'numbers': ["623"],
+ 'fg-color': '#fff',
+ 'bg-color': '#fe6612'
+ },
+ { 'id': 158,
+ 'name': u"Nancy République / Champigneulles",
+ 'numbers': ["321"],
+ 'fg-color': '#fff',
+ 'bg-color': '#d3b6d1'
+ },
+ { 'id': 126,
+ 'name': u"Vandoeuvre Parc des Expositions / Nancy République",
+ 'numbers': ["129"],
+ 'fg-color': '#fff',
+ 'bg-color': '#92bbad'
+ },
+ { 'id': 133,
+ 'name': u"Ligne 80",
+ 'numbers': ["80"],
+ 'bg-color': 'yellow'
+ },
+ { 'id': 138,
+ 'name': u"Vandoeuvre CHU Brabois / Essey Mouzimpré",
+ 'numbers': ["Tram 1"],
+ 'fg-color': '#fff',
+ 'bg-color': '#ff0000'
+ },
+ { 'id': 141,
+ 'name': u"Vandoeuvre CHU Brabois / Centre d\'accueil - Villers les Essarts",
+ 'numbers': ["Ptit Stan Vilers"],
+ 'bg-color': 'yellow'
+ },
+ { 'id': 143,
+ 'name': u"Vandoeuvre CHU Brabois / Vandoeuvre Nations - Médecine préventive ",
+ 'numbers': ["Ptit Stan Vandoeuvre"],
+ 'bg-color': 'yellow'
+ },
+ { 'id': 140,
+ 'name': u"Laxou Sapinière / Laxou Provinces",
+ 'numbers': ["Ptit Stan Laxou"],
+ 'bg-color': 'yellow'
+ },
+ { 'id': 142,
+ 'name': u"Nancy Carnot",
+ 'numbers': ["Ptit Stan Colline"],
+ 'bg-color': 'yellow'
+ },
+ { 'id': 134,
+ 'name': u"Malzéville Sadi Carnot - Nancy Cimetière du Sud",
+ 'numbers': ["Minibus"],
+ 'bg-color': 'yellow'
+ },
+ { 'id': 159,
+ 'name': u"StanU",
+ 'numbers': ["110"],
+ 'bg-color': 'yellow'
+ },
+ { 'id': 112,
+ 'name': u"Maxéville Champ le Boeuf - Nancy Haut du Lièvre / Jarville Sion",
+ 'numbers': ["111", "112"],
+ 'fg-color': '#fff',
+ 'bg-color': '#71228d'
+ },
+ { 'id': 96,
+ 'name': u"Houdemont / Vandoeuvre Charmois",
+ 'numbers': ["114"],
+ 'fg-color': '#000',
+ 'bg-color': '#b6a6d3'
+ },
+ { 'id': 98,
+ 'name': u"Ludres / Villers Lycée Stanislas",
+ 'numbers': ["115"],
+ 'fg-color': '#fff',
+ 'bg-color': '#705aac'
+ },
+ { 'id': 101,
+ 'name': u"Fléville Fleurychamp - Heillecourt / Villers Lycée Stanislas",
+ 'numbers': ["116"],
+ 'fg-color': '#fff',
+ 'bg-color': '#755eac'
+ },
+ { 'id': 103,
+ 'name': u"Nancy Beauregard / Nancy République",
+ 'numbers': ["121"],
+ 'fg-color': '#fff',
+ 'bg-color': '#339d2f'
+ },
+ { 'id': 135,
+ 'name': u"Villers Clairlieu / Nancy République",
+ 'numbers': ["122", "126"],
+ 'fg-color': '#000',
+ 'bg-color': '#9cca1f'
+ },
+ { 'id': 106,
+ 'name': u"Vandoeuvre Nations / Nancy République",
+ 'numbers': ["123"],
+ 'fg-color': '#000',
+ 'bg-color': '#a9d72f'
+ },
+ { 'id': 109,
+ 'name': u"Laxou Mouzon / Nancy République",
+ 'numbers': ["124"],
+ 'fg-color': '#fff',
+ 'bg-color': '#39a63b'
+ },
+
+ { 'id': 118,
+ 'name': u"Laxou Provinces / Seichamps",
+ 'numbers': ["130"],
+ 'fg-color': '#fff',
+ 'bg-color': '#048a96'
+ },
+ { 'id': 95,
+ 'name': u"Maxéville Mairie / Jarville Sion - Heillecourt - Fléville De la Noue",
+ 'numbers': ["131", "132", "133"],
+ 'fg-color': '#000',
+ 'bg-color': '#82cee6'
+ },
+ { 'id': 97,
+ 'name': u"Villers Lycée Stanislas / Maxéville Lafayette - Malzéville Savlons",
+ 'numbers': ["134", "135"],
+ 'fg-color': '#fff',
+ 'bg-color': '#1693cb'
+ },
+ { 'id': 100,
+ 'name': u"Nancy Cours Léopold / Laneuveville - La Madeleine",
+ 'numbers': ["136", "137"],
+ 'fg-color': '#fff',
+ 'bg-color': '#6f88c1'
+ },
+ { 'id': 102,
+ 'name': u"Laxou Champ le Boeuf / Vandoeurvre Roberval",
+ 'numbers': ["138"],
+ 'fg-color': '#fff',
+ 'bg-color': '#123e97'
+ },
+ { 'id': 104,
+ 'name': u"Nancy Cours Léopold / Ludres",
+ 'numbers': ["139"],
+ 'fg-color': '#fff',
+ 'bg-color': '#6f87c7'
+ },
+ { 'id': 114,
+ 'name': u"Malzéville / Saulxures Forêt - Art sur Meurthe",
+ 'numbers': ["141", "142"],
+ 'fg-color': '#fff',
+ 'bg-color': '#ff750e'
+ },
+ { 'id': 108,
+ 'name': u"Malzéville Pixérécourt / Saulxures",
+ 'numbers': ["161"],
+ 'fg-color': '#000',
+ 'bg-color': '#ffff33'
+ },
+ { 'id': 116,
+ 'name': u"Essey C.E.S / Gérard Barrois Stade Marcel Picot",
+ 'numbers': ["162"],
+ 'fg-color': '#000',
+ 'bg-color': '#f59638'
+ },
+ { 'id': 107,
+ 'name': u"Dommartemont / Jarville Sion",
+ 'numbers': ["163"],
+ 'fg-color': '#000',
+ 'bg-color': '#fad956'
+ },
+ { 'id': 110,
+ 'name': u"Essey Mouzimpré / Seichamps - Pulnoy (Lignes accessibles PMR)",
+ 'numbers': ["171", "172"],
+ 'bg-color': '#f38ab3'
+ }
+ ]
+
+ def schedules_cache_dirname(self):
+ pystan_schedules = os.path.expanduser('~/.pystan/cache/schedules')
+ if not os.path.isdir(pystan_schedules):
+ #os.makedirs('~/.pystan/cache/schedules')
+ pass
+ return pystan_schedules
+
+ def filter_params(self, params):
+ only_wanted = {}
+ for key in params:
+ if key in self.get_params:
+ only_wanted[key] = params[key]
+ return only_wanted
+
+ def schedule_cache_filename(self, params):
+ params['date'] = self.schedules_cache_dateformat(params['date'])
+ params = [ str(key)+"="+str(params[key]) for key in iter(params)]
+ params.sort()
+ filename = '_'.join(params)
+ filename = filename.replace('/', '-')
+ return "%s/%s" % (self.schedules_cache_dirname(), filename)
+
+ def schedules_cache_dateformat(self, date):
+ date = time.strptime(date, "%d/%m/%Y")
+ day_n = int(time.strftime("%w", date))
+ # saturday / sunday
+ if day_n == 0 or day_n == 6:
+ date = time.strftime("%a/%m/%Y", date)
+ # week
+ else:
+ date = time.strftime("week/%m/%Y", date)
+ return date
+
+
+ def default_timetable_params(self, params):
+ for param, default in { 'rub_code':6, 'thm_id':6, 'gpl_id':0 }.items(): #{'sens':1, 'date':'01/01/01', 'hour':'12', 'index':'1'}:
+ if not params.has_key(param) or params[param] is None:
+ params[param] = default
+ return params
+
+ def search(self, from_location, from_city, to_location, to_city, datetime, restrict=68):
+ cached_result = self.cached_search(from_location, from_city, to_location, to_city, datetime, restrict)
+ if cached_search is not None:
+ return cached_result
+
+ '''
+ POST to http://www.reseau-stan.com/ri/index.asp
+ from_location: keywordsDep
+ from_city: comDep
+ to_location: keywordsArr
+ to_city: comArr
+ date: laDate (11/03/2009)
+ hour: lHeure (13 = 13h)
+ minute: laMinute
+ restrict: typeDate (68 = partir apres, 65 = arriver avant)
+ '''
+ pass
+
+ def cached_search(self, from_location, from_city, to_location, to_city, datetime, restrict=68):
+ cache_id = '-'.join([ from_location, from_city, to_location, to_city, datetime, restrict ])
+ pass
+
+ def load_timetable(self, lineid, params=None, bypass=False):
+ params = self.default_timetable_params(params)
+ params['lineid'] = lineid
+ params = self.filter_params(params)
+
+ timetable_raw_html_file = None
+
+ if timetable_raw_html_file is None:
+ timetable_raw_html_file = self.load_timetable_local(params, bypass)
+
+ if timetable_raw_html_file is None:
+ timetable_raw_html_file = self.load_timetable_remote(params)
+
+ if timetable_raw_html_file is None:
+ print "WARNING:\tNo cache file and no internet connection"
+
+ if timetable_raw_html_file:
+ parser = StanTimetableParser()
+ parser.feed(timetable_raw_html_file)
+ parser.close()
+ parsed = parser.result
+
+ if parsed['navigation'].has_key('next'):
+ index_next = urlparse.urlsplit(parsed['navigation']['next'])[3]
+ index_next = cgi.parse_qs(index_next)
+ parsed['navigation']['next'] = index_next['index'][0]
+
+ if parsed['navigation'].has_key('prev'):
+ index_prev = urlparse.urlsplit(parsed['navigation']['prev'])[3]
+ index_prev = cgi.parse_qs(index_prev)
+ parsed['navigation']['prev'] = index_prev['index'][0]
+
+ if len(parsed['stations']) > 0:
+ parsed['stations'] = self.extract_stations_infos(parsed['stations'])
+ pass
+
+ return parsed
+
+ else:
+ return timetable_raw_html_file
+
+ def load_timetable_remote(self, params):
+ url = 'http://www.reseau-stan.com/horaires_ligne/index.asp'
+
+ get_params = params.copy()
+ get_params['lign_id'] = get_params['lineid']; del(get_params['lineid'])
+
+ if get_params.has_key('direction') and int(get_params['direction']) > 0:
+ get_params['sens'] = 2
+ del(get_params['direction'])
+ else:
+ get_params['sens'] = 1
+
+ if get_params.has_key('hour'):
+ get_params['index'] = get_params['hour']
+ del(get_params['hour'])
+
+ req = urllib2.Request(url, urllib.urlencode(get_params))
+
+ print "GET FROM:\t%s?%s" % (url, urllib.urlencode(get_params))
+
+ file_data = None
+
+ try:
+ response = urllib2.urlopen(req)
+ file_data = response.read()
+ except URLError, e:
+ print "WARNING:\t%s" % e
+
+ if file_data is not None:
+ self.save_timetable_local(file_data, params)
+ return file_data
+
+ def load_timetable_local(self, params, bypass=False):
+ file = self.schedule_cache_filename(params)
+ file_data = None
+ max_age = 999L
+
+ print "GET FROM:\t%s" % file
+
+ if not os.path.isfile(file):
+ #print "WARNING: %s does not exists" % file
+ file_data = None
+
+ elif bypass or os.stat(file).st_mtime <= int(time.time()) + max_age:
+ response = open(file)
+ try:
+ file_data = response.read()
+ finally:
+ response.close()
+ else:
+ print "WARNING:\t%s exists but is too old" % file
+ file_data = False
+
+ return file_data
+
+ def save_timetable_local(self, file_data, params):
+
+ file_data = re.compile('(<div id="goatResult">.*)<div id="footer">', re.DOTALL).findall(file_data)
+
+ if len(file_data) != 1:
+ print "ERROR:\tCannot store a valid html section (%s occurences found)" % len(file_data)
+ return False
+ else:
+ file_data = file_data[0]
+
+ file = self.schedule_cache_filename(params)
+ print "WRITING:\t%s" % file
+ file_dest = open(file, 'w')
+ file_dest.write(file_data)
+ file_dest.close()
+ return True
+
+ def extract_stations_infos(self, stations_urls):
+ stations_ids = []
+ for station_url in stations_urls:
+ station_id = urlparse.urlsplit(station_url)[3]
+ station_id = cgi.parse_qs(station_id)
+
+ if not station_id.has_key('pa_id1') or len(station_id['pa_id1']) != 1:
+ print "ERROR:\tCannot extract pa_id1 from url %s " % station_url
+ continue
+
+ station_id = station_id['pa_id1'][0]
+
+ url = 'http://www.reseau-stan.com/ri/cartegoogle.asp'
+
+ if False:
+ req = urllib2.Request(url, urllib.urlencode({ 'pa_iad1':station_id }))
+ print "GET FROM:\t%s?%s" % (url, urllib.urlencode(get_params))
+
+ file_data = None
+ try:
+ response = urllib2.urlopen(req)
+ file_data = response.read()
+ except URLError, e:
+ print "WARNING: %s" % e
+
+ stations_ids.append({
+ 'id': station_id,
+ 'href': "http://www.reseau-stan.com/ri/cartegoogle.asp?%s" % urllib.urlencode({ 'pa_iad1':station_id })
+ })
+
+ return stations_ids
+